blob: 5c5224658965f4b25a0fce851bf20b2e38f36f05 [file] [log] [blame]
/*
* 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 &quot;wait forever&quot;.
*
* @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;
}
}
}