package org.jftclient.ssh; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import javax.annotation.PreDestroy; import org.jftclient.JFTText; import org.jftclient.OutputPanel; import org.jftclient.config.dao.ConfigDao; import org.jftclient.tree.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; import javafx.scene.text.Text; /** * @author sergei.malafeev */ @Component public class Connection { private static final Logger logger = LoggerFactory.getLogger(Connection.class); private static final int timeout = 5000; private Session session; private ChannelSftp sftpChannel; private String remoteHost; private String user; private String password; @Autowired private ConfigDao configDao; @PreDestroy public void destroy() { disconnect(); } public synchronized void processSymLink(Node node) { try { String symPath = this.sftpChannel.readlink(node.getPath()); node.setLinkDest(symPath); File file = new File(symPath); if (file.isAbsolute()) { node.setFile(!this.sftpChannel.stat(symPath).isDir()); } String absPath = new File(new File(node.getPath()).getParent(), symPath).getAbsolutePath(); node.setFile(!this.sftpChannel.stat(absPath).isDir()); } catch (SftpException e) { if (e.getMessage() != null && e.getMessage().contains("No such file")) { return; } logger.error("failed to check if dir '{}' is symlink", node.getPath(), e); } } public synchronized List<Node> getNodes(String path) { List<Node> nodes = new ArrayList<>(); if ((session == null) || !session.isConnected()) { return nodes; } for (ChannelSftp.LsEntry entry : getFiles(path)) { Node node = new Node(); nodes.add(node); node.setName(entry.getFilename()); if (path.endsWith("/")) { node.setPath(path + entry.getFilename()); } else { node.setPath(path + "/" + entry.getFilename()); } if (entry.getAttrs().isLink()) { processSymLink(node); } else { node.setFile(!entry.getAttrs().isDir()); } } Collections.sort(nodes); return nodes; } /** * remove path * * @param path absolute path */ public synchronized void rm(String path) { sendCommand("rm -rf " + path); } public synchronized void mv(String src, String dest) { sendCommand("mv -f " + src + " " + dest); } /** * create new directory * * @param path path of new directory */ public synchronized void mkdir(String path) { sendCommand("mkdir -p " + path); } public synchronized void sendCommand(String command) { List<Text> output = new ArrayList<>(); output.add(JFTText.getRemoteHost(remoteHost)); output.add(JFTText.textBlack(command)); try { Channel channel = this.session.openChannel("exec"); ((ChannelExec) channel).setCommand(command); channel.setInputStream(null); OutputStream out = new PipedOutputStream(); channel.setOutputStream(out); OutputStream outErr = new PipedOutputStream(); ((ChannelExec) channel).setErrStream(outErr); PipedInputStream pout = new PipedInputStream((PipedOutputStream) out); PipedInputStream poutErr = new PipedInputStream((PipedOutputStream) outErr); channel.connect(timeout); try (BufferedReader consoleOutput = new BufferedReader( new InputStreamReader(pout, Charset.defaultCharset()))) { String s; while ((s = consoleOutput.readLine()) != null) { output.add(JFTText.textBlack("\n" + s)); } } try (BufferedReader consoleErr = new BufferedReader( new InputStreamReader(poutErr, Charset.defaultCharset()))) { String s; while ((s = consoleErr.readLine()) != null) { output.add(JFTText.textRed("\n" + s)); } } channel.disconnect(); } catch (IOException | JSchException e) { logger.error("failed to send command: {}", command, e); } OutputPanel.getInstance().printlnOutputLater(output); } private synchronized List<ChannelSftp.LsEntry> getFiles(String path) { final List<ChannelSftp.LsEntry> files = new LinkedList<>(); if ((path == null) || path.isEmpty()) { return files; } final boolean showHiddenFiles = configDao.get().isShowHiddenFiles(); try { sftpChannel.ls(path, entry -> { if (!showHiddenFiles) { if (entry.getFilename().startsWith(".")) { return ChannelSftp.LsEntrySelector.CONTINUE; } } else if (entry.getFilename().equals(".") || entry.getFilename().equals("..")) { return ChannelSftp.LsEntrySelector.CONTINUE; } files.add(entry); return ChannelSftp.LsEntrySelector.CONTINUE; }); } catch (SftpException e) { // ignore exception. usually happens because of permission denied } return files; } /** * Connect to remote host * * @param remoteHost * @param user * @param password * @return true if connected otherwise false */ public synchronized ConnectionState connect(String remoteHost, String user, String password) { disconnect(); this.remoteHost = remoteHost; this.user = user; this.password = password; JSch jsch = new JSch(); try { session = jsch.getSession(user, remoteHost, 22); session.setConfig("StrictHostKeyChecking", "no"); session.setPassword(password); session.connect(timeout); // create sftp channel: Channel channel = session.openChannel("sftp"); channel.connect(timeout); sftpChannel = (ChannelSftp) channel; } catch (JSchException e) { logger.debug("failed to connect to {}", remoteHost, e); return new ConnectionState(e, "failed to connect to " + remoteHost); } return new ConnectionState(); } public synchronized boolean isConnected() { if (session == null || !session.isConnected()) { return false; } return true; } /** * disconnect from remote host */ public synchronized void disconnect() { if (sftpChannel != null) { sftpChannel.exit(); } if (session != null && session.isConnected()) { session.disconnect(); } } public synchronized String getUser() { return user; } public synchronized String getPassword() { return password; } public synchronized String getRemoteHost() { return remoteHost; } }