All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.jsch.tunnel.TunnelConnectionManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed 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
 *
 *         http://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 com.helger.jsch.tunnel;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.CommonsHashSet;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsSet;
import com.helger.commons.io.file.FileHelper;
import com.helger.commons.io.stream.NonBlockingBufferedReader;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.string.StringHelper;
import com.helger.jsch.proxy.SshProxy;
import com.helger.jsch.session.AbstractSessionFactoryBuilder;
import com.helger.jsch.session.ISessionFactory;
import com.jcraft.jsch.JSchException;

/**
 * Manages a collection of tunnels. This implementation will:
 * 
    *
  • Ensure a minimum number of ssh connections are made
  • *
  • Ensure all connections are open/closed at the same time
  • *
  • Provide a convenient syntax for defining tunnels
  • *
*/ public class TunnelConnectionManager implements Closeable { private static final Pattern PATTERN_TUNNELS_CFG_COMMENT_LINE = Pattern.compile ("^\\s*(?:#.*)?$"); private static final Logger LOGGER = LoggerFactory.getLogger (TunnelConnectionManager.class); private final ISessionFactory m_aBaseSessionFactory; private ICommonsList m_aTunnelConnections; /** * Creates a TunnelConnectionManager that will use the * baseSessionFactory to obtain its session connections. Because * this constructor does not set the tunnel connections for you, you will need * to call {@link #setTunnelConnections(Iterable)}. * * @param baseSessionFactory * The session factory * @throws JSchException * For connection failures * @see #setTunnelConnections(Iterable) */ public TunnelConnectionManager (final ISessionFactory baseSessionFactory) throws JSchException { if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Creating TunnelConnectionManager"); m_aBaseSessionFactory = baseSessionFactory; } /** * Creates a TunnelConnectionManager that will use the * baseSessionFactory to obtain its session connections and * provide the tunnels specified. * * @param baseSessionFactory * The session factory * @param pathAndSpecList * A list of {@link #setTunnelConnections(Iterable) path and spec} * strings * @throws JSchException * For connection failures * @see #setTunnelConnections(Iterable) */ public TunnelConnectionManager (final ISessionFactory baseSessionFactory, final String... pathAndSpecList) throws JSchException { this (baseSessionFactory, Arrays.asList (pathAndSpecList)); } /** * Creates a TunnelConnectionManager that will use the * baseSessionFactory to obtain its session connections and * provide the tunnels specified. * * @param baseSessionFactory * The session factory * @param pathAndSpecList * A list of {@link #setTunnelConnections(Iterable) path and spec} * strings * @throws JSchException * For connection failures * @see #setTunnelConnections(Iterable) */ public TunnelConnectionManager (final ISessionFactory baseSessionFactory, final Iterable pathAndSpecList) throws JSchException { this (baseSessionFactory); setTunnelConnections (pathAndSpecList); } /** * Closes all sessions and their associated tunnels. * * @see com.helger.jsch.tunnel.TunnelConnection#close() */ @Override public void close () { for (final TunnelConnection tunnelConnection : m_aTunnelConnections) StreamHelper.close (tunnelConnection); } /** * Will re-open any connections that are not still open. * * @throws JSchException * For connection failures */ public void ensureOpen () throws JSchException { for (final TunnelConnection tunnelConnection : m_aTunnelConnections) if (!tunnelConnection.isOpen ()) tunnelConnection.reopen (); } /** * Returns the tunnel matching the supplied values, or null if * there isn't one that matches. * * @param destinationHostname * The tunnels destination hostname * @param destinationPort * The tunnels destination port * @return The tunnel matching the supplied values * @see com.helger.jsch.tunnel.TunnelConnection#getTunnel(String, int) */ public Tunnel getTunnel (final String destinationHostname, final int destinationPort) { // might be better to cache, but dont anticipate massive numbers // of tunnel connections... for (final TunnelConnection tunnelConnection : m_aTunnelConnections) { final Tunnel tunnel = tunnelConnection.getTunnel (destinationHostname, destinationPort); if (tunnel != null) return tunnel; } return null; } /** * Opens all the necessary sessions and connects all of the tunnels. * * @throws JSchException * For connection failures * @see com.helger.jsch.tunnel.TunnelConnection#open() */ public void open () throws JSchException { for (final TunnelConnection tunnelConnection : m_aTunnelConnections) tunnelConnection.open (); } /** * Creates a set of tunnel connections based upon the contents of * tunnelsConfig. The format of this file is one path and tunnel * per line. Comments and empty lines are allowed and are excluded using the * pattern ^\s*(?:#.*)?$. * * @param tunnelsConfig * A file containing tunnel configuration * @param aCharset * Charset to use. May not be null. * @throws IOException * If unable to read from tunnelsConfig * @throws JSchException * For connection failures */ public void setTunnelConnectionsFromFile (final File tunnelsConfig, @Nonnull final Charset aCharset) throws IOException, JSchException { final List aLines = new ArrayList <> (); try (final NonBlockingBufferedReader reader = new NonBlockingBufferedReader (FileHelper.getReader (tunnelsConfig, aCharset))) { String sLine; while ((sLine = reader.readLine ()) != null) { if (PATTERN_TUNNELS_CFG_COMMENT_LINE.matcher (sLine).matches ()) continue; aLines.add (sLine); } } setTunnelConnections (aLines); } /** * Creates a set of tunnel connections based upon the pathAndTunnels. Each * entry of pathAndTunnels must be of the form (in * EBNF): * *
   * path and tunnels = path and tunnel, {new line, path and tunnel}
   * path and tunnel = path, "|", tunnel
   * new line = "\n"
   * path = path part, {"->", path part}
   * path part = {user, "@"}, hostname
   * tunnel = {local part}, ":", destination hostname, ":", destination port
   * local part = {local alias, ":"}, local port
   * local alias = hostname
   * local port = port
   * destination hostname = hostname
   * destination port = port
   * user = ? user name ?
   * hostname = ? hostname ?
   * port = ? port ?
   * 
*

* For example: *

*

* * [email protected]>[email protected]|drteeth:8080:drteeth.muppets.com:80 * *

*

* Says open an ssh connection as user jimhenson to host * admin.muppets.com. Then, through that connection, open a * connection as user animal to host * drteethandtheelectricmahem.muppets.com. Then map local port * 8080 on the interface with alias drteeth through * the two-hop tunnel to port 80 on * drteeth.muppets.com. *

* * @param aPathAndSpecList * A list of path and spec entries * @throws JSchException * For connection failures */ public void setTunnelConnections (@Nonnull final Iterable aPathAndSpecList) throws JSchException { final Map > aMap = new HashMap <> (); for (final String sItem : aPathAndSpecList) { final String [] aPathAndSpec = StringHelper.getExplodedArray ('|', sItem.trim (), 2); aMap.computeIfAbsent (aPathAndSpec[0], k -> new CommonsHashSet <> ()).add (new Tunnel (aPathAndSpec[1])); } m_aTunnelConnections = new CommonsArrayList <> (); final SessionFactoryCache sessionFactoryCache = new SessionFactoryCache (m_aBaseSessionFactory); for (final Map.Entry > aEntry : aMap.entrySet ()) { final String path = aEntry.getKey (); m_aTunnelConnections.add (new TunnelConnection (sessionFactoryCache.getSessionFactory (path), aEntry.getValue ().getCopyAsList ())); } } /* * Used to ensure duplicate paths are not created which will minimize the * number of connections needed. */ static class SessionFactoryCache { private final Map sessionFactoryByPath; private final ISessionFactory defaultSessionFactory; SessionFactoryCache (final ISessionFactory baseSessionFactory) { this.defaultSessionFactory = baseSessionFactory; this.sessionFactoryByPath = new HashMap <> (); } public ISessionFactory getSessionFactory (final String path) throws JSchException { ISessionFactory sessionFactory = null; final StringBuilder key = new StringBuilder (); for (final String part : StringHelper.getExploded ("->", path)) { if (key.length () > 0) key.append ("->"); key.append (part); final String sKey = key.toString (); if (sessionFactoryByPath.containsKey (sKey)) return sessionFactoryByPath.get (sKey); final AbstractSessionFactoryBuilder builder; if (sessionFactory == null) builder = defaultSessionFactory.newSessionFactoryBuilder (); else builder = sessionFactory.newSessionFactoryBuilder ().setProxy (new SshProxy (sessionFactory)); // start with [username@]hostname[:port] final String [] aUserAtHost = StringHelper.getExplodedArray ('@', part, 2); final String sHostname; if (aUserAtHost.length == 2) { builder.setUsername (aUserAtHost[0]); sHostname = aUserAtHost[1]; } else { sHostname = aUserAtHost[0]; } // left with hostname[:port] final String [] hostColonPort = StringHelper.getExplodedArray (':', sHostname, 2); builder.setHostname (hostColonPort[0]); if (hostColonPort.length == 2) { builder.setPort (Integer.parseInt (hostColonPort[1])); } sessionFactory = builder.build (); } return sessionFactory; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy