| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs.optional.ssh; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.TaskContainer; |
| import com.jcraft.jsch.JSchException; |
| import com.jcraft.jsch.Session; |
| |
| |
| /** |
| * Establishes an ssh session with a remote machine, optionally |
| * establishing port forwarding, then executes any nested task(s) |
| * before closing the session. |
| * @since Ant 1.8.0 |
| */ |
| public class SSHSession extends SSHBase { |
| |
| /** units are milliseconds, default is 0=infinite */ |
| private long maxwait = 0; |
| |
| private final List<LocalTunnel> localTunnels = new Vector<>(); |
| private final Set<Integer> localPortsUsed = new TreeSet<>(); |
| private final List<RemoteTunnel> remoteTunnels = new Vector<>(); |
| private final Set<Integer> remotePortsUsed = new TreeSet<>(); |
| private NestedSequential nestedSequential = null; |
| |
| private static final String TIMEOUT_MESSAGE = |
| "Timeout period exceeded, connection dropped."; |
| |
| |
| /** Optional Vector holding the nested tasks */ |
| private final List<Task> nestedTasks = new Vector<>(); |
| |
| /** |
| * Add a nested task to Sequential. |
| * |
| * @param nestedTask Nested task to execute sequentially |
| */ |
| public void addTask(final Task nestedTask) { |
| nestedTasks.add(nestedTask); |
| } |
| |
| /** |
| * The connection can be dropped after a specified number of |
| * milliseconds. This is sometimes useful when a connection may be |
| * flaky. Default is 0, which means "wait forever". |
| * |
| * @param timeout The new timeout value in seconds |
| */ |
| public void setTimeout(final long timeout) { |
| maxwait = timeout; |
| } |
| |
| /** |
| * Changes the comma-delimited list of local tunnels to establish |
| * on the connection. |
| * |
| * @param tunnels a comma-delimited list of lport:rhost:rport |
| * tunnel specifications |
| */ |
| public void setLocaltunnels(final String tunnels) { |
| for (String tunnelSpec : tunnels.split(", ")) { |
| if (!tunnelSpec.isEmpty()) { |
| final String[] spec = tunnelSpec.split(":", 3); |
| final int lport = Integer.parseInt(spec[0]); |
| final String rhost = spec[1]; |
| final int rport = Integer.parseInt(spec[2]); |
| final LocalTunnel tunnel = createLocalTunnel(); |
| tunnel.setLPort(lport); |
| tunnel.setRHost(rhost); |
| tunnel.setRPort(rport); |
| } |
| } |
| } |
| |
| /** |
| * Changes the comma-delimited list of remote tunnels to establish |
| * on the connection. |
| * |
| * @param tunnels a comma-delimited list of rport:lhost:lport |
| * tunnel specifications |
| */ |
| public void setRemotetunnels(final String tunnels) { |
| for (String tunnelSpec : tunnels.split(", ")) { |
| if (!tunnelSpec.isEmpty()) { |
| final String[] spec = tunnelSpec.split(":", 3); |
| final int rport = Integer.parseInt(spec[0]); |
| final String lhost = spec[1]; |
| final int lport = Integer.parseInt(spec[2]); |
| final RemoteTunnel tunnel = createRemoteTunnel(); |
| tunnel.setRPort(rport); |
| tunnel.setLHost(lhost); |
| tunnel.setLPort(lport); |
| } |
| } |
| } |
| |
| |
| /** |
| * Establish the ssh session and execute all nestedTasks |
| * |
| * @exception BuildException if one of the nested tasks fails, or |
| * network error or bad parameter. |
| */ |
| @Override |
| public void execute() throws BuildException { |
| if (getHost() == null) { |
| throw new BuildException("Host is required."); |
| } |
| |
| loadSshConfig(); |
| |
| if (getUserInfo().getName() == null) { |
| throw new BuildException("Username is required."); |
| } |
| if (getUserInfo().getKeyfile() == null |
| && getUserInfo().getPassword() == null) { |
| throw new BuildException("Password or Keyfile is required."); |
| } |
| if (nestedSequential == null) { |
| throw new BuildException("Missing sequential element."); |
| } |
| |
| Session session = null; |
| try { |
| // establish the session |
| session = openSession(); |
| session.setTimeout((int) maxwait); |
| |
| for (LocalTunnel tunnel : localTunnels) { |
| session.setPortForwardingL(tunnel.getLPort(), |
| tunnel.getRHost(), |
| tunnel.getRPort()); |
| } |
| |
| for (RemoteTunnel tunnel : remoteTunnels) { |
| session.setPortForwardingR(tunnel.getRPort(), |
| tunnel.getLHost(), |
| tunnel.getLPort()); |
| } |
| |
| nestedSequential.getNested().forEach(Task::perform); |
| // completed successfully |
| |
| } catch (final JSchException e) { |
| if (e.getMessage().contains("session is down")) { |
| if (getFailonerror()) { |
| throw new BuildException(TIMEOUT_MESSAGE, e); |
| } |
| log(TIMEOUT_MESSAGE, Project.MSG_ERR); |
| } else { |
| if (getFailonerror()) { |
| throw new BuildException(e); |
| } |
| log("Caught exception: " + e.getMessage(), Project.MSG_ERR); |
| } |
| } catch (final BuildException e) { |
| // avoid wrapping it into yet another BuildException further down |
| throw e; |
| } catch (final Exception e) { |
| if (getFailonerror()) { |
| throw new BuildException(e); |
| } |
| log("Caught exception: " + e.getMessage(), Project.MSG_ERR); |
| } finally { |
| if (session != null && session.isConnected()) { |
| session.disconnect(); |
| } |
| } |
| } |
| |
| public LocalTunnel createLocalTunnel() { |
| final LocalTunnel tunnel = new LocalTunnel(); |
| localTunnels.add(tunnel); |
| return tunnel; |
| } |
| |
| public RemoteTunnel createRemoteTunnel() { |
| final RemoteTunnel tunnel = new RemoteTunnel(); |
| remoteTunnels.add(tunnel); |
| return tunnel; |
| } |
| |
| public class LocalTunnel { |
| |
| int lport = 0; |
| String rhost = null; |
| int rport = 0; |
| |
| public void setLPort(final int lport) { |
| final Integer portKey = lport; |
| if (localPortsUsed.contains(portKey)) { |
| throw new BuildException( |
| "Multiple local tunnels defined to use same local port %d", |
| lport); |
| } |
| localPortsUsed.add(portKey); |
| this.lport = lport; |
| } |
| |
| public void setRHost(final String rhost) { |
| this.rhost = rhost; |
| } |
| |
| public void setRPort(final int rport) { |
| this.rport = rport; |
| } |
| |
| public int getLPort() { |
| if (lport == 0) { |
| throw new BuildException("lport is required for LocalTunnel."); |
| } |
| return lport; |
| } |
| |
| public String getRHost() { |
| if (rhost == null) { |
| throw new BuildException("rhost is required for LocalTunnel."); |
| } |
| return rhost; |
| } |
| |
| public int getRPort() { |
| if (rport == 0) { |
| throw new BuildException("rport is required for LocalTunnel."); |
| } |
| return rport; |
| } |
| } |
| |
| public class RemoteTunnel { |
| |
| int lport = 0; |
| String lhost = null; |
| int rport = 0; |
| |
| public void setLPort(final int lport) { |
| this.lport = lport; |
| } |
| |
| public void setLHost(final String lhost) { |
| this.lhost = lhost; |
| } |
| |
| public void setRPort(final int rport) { |
| final Integer portKey = rport; |
| if (remotePortsUsed.contains(portKey)) { |
| throw new BuildException( |
| "Multiple remote tunnels defined to use same remote port %d", |
| rport); |
| } |
| remotePortsUsed.add(portKey); |
| this.rport = rport; |
| } |
| |
| public int getLPort() { |
| if (lport == 0) { |
| throw new BuildException("lport is required for RemoteTunnel."); |
| } |
| return lport; |
| } |
| |
| public String getLHost() { |
| if (lhost == null) { |
| throw new BuildException("lhost is required for RemoteTunnel."); |
| } |
| return lhost; |
| } |
| |
| public int getRPort() { |
| if (rport == 0) { |
| throw new BuildException("rport is required for RemoteTunnel."); |
| } |
| return rport; |
| } |
| } |
| |
| /** |
| * This is the sequential nested element of the macrodef. |
| * |
| * @return a sequential element to be configured. |
| */ |
| public NestedSequential createSequential() { |
| if (this.nestedSequential != null) { |
| throw new BuildException("Only one sequential allowed"); |
| } |
| this.nestedSequential = new NestedSequential(); |
| return this.nestedSequential; |
| } |
| |
| /** |
| * The class corresponding to the sequential nested element. |
| * This is a simple task container. |
| */ |
| public static class NestedSequential implements TaskContainer { |
| private final List<Task> nested = new ArrayList<>(); |
| |
| /** |
| * Add a task or type to the container. |
| * |
| * @param task an unknown element. |
| */ |
| @Override |
| public void addTask(final Task task) { |
| nested.add(task); |
| } |
| |
| /** |
| * @return the list of unknown elements |
| */ |
| public List<Task> getNested() { |
| return nested; |
| } |
| } |
| |
| } |