AbstractClientProxyConnector.java

  1. /*
  2.  * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.internal.transport.sshd.proxy;

  11. import java.net.InetSocketAddress;
  12. import java.util.ArrayList;
  13. import java.util.Arrays;
  14. import java.util.List;
  15. import java.util.concurrent.Callable;
  16. import java.util.concurrent.TimeUnit;
  17. import java.util.concurrent.atomic.AtomicReference;

  18. import org.apache.sshd.client.session.ClientSession;
  19. import org.eclipse.jgit.annotations.NonNull;
  20. import org.eclipse.jgit.internal.transport.sshd.JGitClientSession;

  21. /**
  22.  * Basic common functionality for a {@link StatefulProxyConnector}.
  23.  */
  24. public abstract class AbstractClientProxyConnector
  25.         implements StatefulProxyConnector {

  26.     private static final long DEFAULT_PROXY_TIMEOUT_MILLIS = TimeUnit.SECONDS
  27.             .toMillis(30L);

  28.     /** Guards {@link #done} and {@link #bufferedCommands}. */
  29.     private Object lock = new Object();

  30.     private boolean done;

  31.     private List<Callable<Void>> bufferedCommands = new ArrayList<>();

  32.     private AtomicReference<Runnable> unregister = new AtomicReference<>();

  33.     private long remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;

  34.     private long lastProxyOperationTime = 0L;

  35.     /** The ultimate remote address to connect to. */
  36.     protected final InetSocketAddress remoteAddress;

  37.     /** The proxy address. */
  38.     protected final InetSocketAddress proxyAddress;

  39.     /** The user to authenticate at the proxy with. */
  40.     protected String proxyUser;

  41.     /** The password to use for authentication at the proxy. */
  42.     protected char[] proxyPassword;

  43.     /**
  44.      * Creates a new {@link AbstractClientProxyConnector}.
  45.      *
  46.      * @param proxyAddress
  47.      *            of the proxy server we're connecting to
  48.      * @param remoteAddress
  49.      *            of the target server to connect to
  50.      * @param proxyUser
  51.      *            to authenticate at the proxy with; may be {@code null}
  52.      * @param proxyPassword
  53.      *            to authenticate at the proxy with; may be {@code null}
  54.      */
  55.     public AbstractClientProxyConnector(@NonNull InetSocketAddress proxyAddress,
  56.             @NonNull InetSocketAddress remoteAddress, String proxyUser,
  57.             char[] proxyPassword) {
  58.         this.proxyAddress = proxyAddress;
  59.         this.remoteAddress = remoteAddress;
  60.         this.proxyUser = proxyUser;
  61.         this.proxyPassword = proxyPassword == null ? new char[0]
  62.                 : proxyPassword;
  63.     }

  64.     /**
  65.      * Initializes this instance. Installs itself as proxy handler on the
  66.      * session.
  67.      *
  68.      * @param session
  69.      *            to initialize for
  70.      */
  71.     protected void init(ClientSession session) {
  72.         remainingProxyProtocolTime = session.getLongProperty(
  73.                 StatefulProxyConnector.TIMEOUT_PROPERTY,
  74.                 DEFAULT_PROXY_TIMEOUT_MILLIS);
  75.         if (remainingProxyProtocolTime <= 0L) {
  76.             remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;
  77.         }
  78.         if (session instanceof JGitClientSession) {
  79.             JGitClientSession s = (JGitClientSession) session;
  80.             unregister.set(() -> s.setProxyHandler(null));
  81.             s.setProxyHandler(this);
  82.         } else {
  83.             // Internal error, no translation
  84.             throw new IllegalStateException(
  85.                     "Not a JGit session: " + session.getClass().getName()); //$NON-NLS-1$
  86.         }
  87.     }

  88.     /**
  89.      * Obtains the timeout for the whole rest of the proxy connection protocol.
  90.      *
  91.      * @return the timeout in milliseconds, always > 0L
  92.      */
  93.     protected long getTimeout() {
  94.         long last = lastProxyOperationTime;
  95.         long now = System.nanoTime();
  96.         lastProxyOperationTime = now;
  97.         long remaining = remainingProxyProtocolTime;
  98.         if (last != 0L) {
  99.             long elapsed = now - last;
  100.             remaining -= elapsed;
  101.             if (remaining < 0L) {
  102.                 remaining = 10L; // Give it grace period.
  103.             }
  104.         }
  105.         remainingProxyProtocolTime = remaining;
  106.         return remaining;
  107.     }

  108.     /**
  109.      * Adjusts the timeout calculation to not account of elapsed time since the
  110.      * last time the timeout was gotten. Can be used for instance to ignore time
  111.      * spent in user dialogs be counted against the overall proxy connection
  112.      * protocol timeout.
  113.      */
  114.     protected void adjustTimeout() {
  115.         lastProxyOperationTime = System.nanoTime();
  116.     }

  117.     /**
  118.      * Sets the "done" flag.
  119.      *
  120.      * @param success
  121.      *            whether the connector terminated successfully.
  122.      * @throws Exception
  123.      *             if starting ssh fails
  124.      */
  125.     protected void setDone(boolean success) throws Exception {
  126.         List<Callable<Void>> buffered;
  127.         Runnable unset = unregister.getAndSet(null);
  128.         if (unset != null) {
  129.             unset.run();
  130.         }
  131.         synchronized (lock) {
  132.             done = true;
  133.             buffered = bufferedCommands;
  134.             bufferedCommands = null;
  135.         }
  136.         if (success && buffered != null) {
  137.             for (Callable<Void> starter : buffered) {
  138.                 starter.call();
  139.             }
  140.         }
  141.     }

  142.     @Override
  143.     public void runWhenDone(Callable<Void> starter) throws Exception {
  144.         synchronized (lock) {
  145.             if (!done) {
  146.                 bufferedCommands.add(starter);
  147.                 return;
  148.             }
  149.         }
  150.         starter.call();
  151.     }

  152.     /**
  153.      * Clears the proxy password.
  154.      */
  155.     protected void clearPassword() {
  156.         Arrays.fill(proxyPassword, '\000');
  157.         proxyPassword = new char[0];
  158.     }
  159. }