/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. */ package com.ubergeek42.weechat.relay.connection; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.UIKeyboardInteractive; import com.jcraft.jsch.UserInfo; import com.ubergeek42.weechat.relay.JschLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.Socket; import java.nio.channels.ClosedByInterruptException; import java.util.regex.Pattern; public class SSHConnection extends AbstractConnection { protected static Logger logger = LoggerFactory.getLogger("SSHConnection"); private Session sshSession; private Socket sock; private String sshPassword; private String server; private int port; public SSHConnection(String server, int port, String sshHost, int sshPort, String sshUsername, String sshPassword, byte[] sshKey, byte[] sshKnownHosts) throws JSchException { this.server = server; this.port = port; this.sshPassword = sshPassword; JSch.setLogger(new JschLogger()); JSch jsch = new JSch(); jsch.setKnownHosts(new ByteArrayInputStream(sshKnownHosts)); jsch.setConfig("PreferredAuthentications", "password,publickey"); boolean useKeyFile = sshKey != null && sshKey.length > 0; if (useKeyFile) jsch.addIdentity("key", sshKey, null, sshPassword.getBytes()); sshSession = jsch.getSession(sshUsername, sshHost, sshPort); sshSession.setSocketFactory(new SocketChannelFactory()); if (!useKeyFile) sshSession.setUserInfo(new WeechatUserInfo()); } @Override protected void doConnect() throws Exception { try { sshSession.connect(); } catch (RuntimeException e) { throw e.getCause() instanceof ClosedByInterruptException ? (ClosedByInterruptException) e.getCause() : e; } int localPort = sshSession.setPortForwardingL(0, server, port); sock = new Socket("127.0.0.1", localPort); out = sock.getOutputStream(); in = sock.getInputStream(); } @Override public void doDisconnect() { super.doDisconnect(); sshSession.disconnect(); try {sock.close();} catch (IOException | NullPointerException ignored) {} } // this class is preferred than sshSession.setPassword() // as it provides a better password prompt matching on some systems final private static Pattern PASSWORD_PROMPT = Pattern.compile("(?i)password[^:]*:"); private class WeechatUserInfo implements UserInfo, UIKeyboardInteractive { public String getPassphrase() {return null;} public String getPassword() {return sshPassword;} public boolean promptPassphrase(String message) {return false;} public boolean promptPassword(String message) {return true;} public boolean promptYesNo(String message) {return false;} public void showMessage(String message) {} public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) { return (prompt.length == 1 && !echo[0] && PASSWORD_PROMPT.matcher(prompt[0]).find()) ? new String[]{sshPassword} : null; } } }