View Javadoc
1   /*
2    * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.internal.transport.sshd.proxy;
44  
45  import java.net.InetSocketAddress;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.List;
49  import java.util.concurrent.Callable;
50  import java.util.concurrent.TimeUnit;
51  import java.util.concurrent.atomic.AtomicReference;
52  
53  import org.apache.sshd.client.session.ClientSession;
54  import org.eclipse.jgit.annotations.NonNull;
55  import org.eclipse.jgit.internal.transport.sshd.JGitClientSession;
56  
57  /**
58   * Basic common functionality for a {@link StatefulProxyConnector}.
59   */
60  public abstract class AbstractClientProxyConnector
61  		implements StatefulProxyConnector {
62  
63  	private static final long DEFAULT_PROXY_TIMEOUT_MILLIS = TimeUnit.SECONDS
64  			.toMillis(30L);
65  
66  	/** Guards {@link #done} and {@link #bufferedCommands}. */
67  	private Object lock = new Object();
68  
69  	private boolean done;
70  
71  	private List<Callable<Void>> bufferedCommands = new ArrayList<>();
72  
73  	private AtomicReference<Runnable> unregister = new AtomicReference<>();
74  
75  	private long remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;
76  
77  	private long lastProxyOperationTime = 0L;
78  
79  	/** The ultimate remote address to connect to. */
80  	protected final InetSocketAddress remoteAddress;
81  
82  	/** The proxy address. */
83  	protected final InetSocketAddress proxyAddress;
84  
85  	/** The user to authenticate at the proxy with. */
86  	protected String proxyUser;
87  
88  	/** The password to use for authentication at the proxy. */
89  	protected char[] proxyPassword;
90  
91  	/**
92  	 * Creates a new {@link AbstractClientProxyConnector}.
93  	 *
94  	 * @param proxyAddress
95  	 *            of the proxy server we're connecting to
96  	 * @param remoteAddress
97  	 *            of the target server to connect to
98  	 * @param proxyUser
99  	 *            to authenticate at the proxy with; may be {@code null}
100 	 * @param proxyPassword
101 	 *            to authenticate at the proxy with; may be {@code null}
102 	 */
103 	public AbstractClientProxyConnector(@NonNull InetSocketAddress proxyAddress,
104 			@NonNull InetSocketAddress remoteAddress, String proxyUser,
105 			char[] proxyPassword) {
106 		this.proxyAddress = proxyAddress;
107 		this.remoteAddress = remoteAddress;
108 		this.proxyUser = proxyUser;
109 		this.proxyPassword = proxyPassword == null ? new char[0]
110 				: proxyPassword;
111 	}
112 
113 	/**
114 	 * Initializes this instance. Installs itself as proxy handler on the
115 	 * session.
116 	 *
117 	 * @param session
118 	 *            to initialize for
119 	 */
120 	protected void init(ClientSession session) {
121 		remainingProxyProtocolTime = session.getLongProperty(
122 				StatefulProxyConnector.TIMEOUT_PROPERTY,
123 				DEFAULT_PROXY_TIMEOUT_MILLIS);
124 		if (remainingProxyProtocolTime <= 0L) {
125 			remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;
126 		}
127 		if (session instanceof JGitClientSession) {
128 			JGitClientSession s = (JGitClientSession) session;
129 			unregister.set(() -> s.setProxyHandler(null));
130 			s.setProxyHandler(this);
131 		} else {
132 			// Internal error, no translation
133 			throw new IllegalStateException(
134 					"Not a JGit session: " + session.getClass().getName()); //$NON-NLS-1$
135 		}
136 	}
137 
138 	/**
139 	 * Obtains the timeout for the whole rest of the proxy connection protocol.
140 	 *
141 	 * @return the timeout in milliseconds, always > 0L
142 	 */
143 	protected long getTimeout() {
144 		long last = lastProxyOperationTime;
145 		long now = System.nanoTime();
146 		lastProxyOperationTime = now;
147 		long remaining = remainingProxyProtocolTime;
148 		if (last != 0L) {
149 			long elapsed = now - last;
150 			remaining -= elapsed;
151 			if (remaining < 0L) {
152 				remaining = 10L; // Give it grace period.
153 			}
154 		}
155 		remainingProxyProtocolTime = remaining;
156 		return remaining;
157 	}
158 
159 	/**
160 	 * Adjusts the timeout calculation to not account of elapsed time since the
161 	 * last time the timeout was gotten. Can be used for instance to ignore time
162 	 * spent in user dialogs be counted against the overall proxy connection
163 	 * protocol timeout.
164 	 */
165 	protected void adjustTimeout() {
166 		lastProxyOperationTime = System.nanoTime();
167 	}
168 
169 	/**
170 	 * Sets the "done" flag.
171 	 *
172 	 * @param success
173 	 *            whether the connector terminated successfully.
174 	 * @throws Exception
175 	 *             if starting ssh fails
176 	 */
177 	protected void setDone(boolean success) throws Exception {
178 		List<Callable<Void>> buffered;
179 		Runnable unset = unregister.getAndSet(null);
180 		if (unset != null) {
181 			unset.run();
182 		}
183 		synchronized (lock) {
184 			done = true;
185 			buffered = bufferedCommands;
186 			bufferedCommands = null;
187 		}
188 		if (success && buffered != null) {
189 			for (Callable<Void> starter : buffered) {
190 				starter.call();
191 			}
192 		}
193 	}
194 
195 	@Override
196 	public void runWhenDone(Callable<Void> starter) throws Exception {
197 		synchronized (lock) {
198 			if (!done) {
199 				bufferedCommands.add(starter);
200 				return;
201 			}
202 		}
203 		starter.call();
204 	}
205 
206 	/**
207 	 * Clears the proxy password.
208 	 */
209 	protected void clearPassword() {
210 		Arrays.fill(proxyPassword, '\000');
211 		proxyPassword = new char[0];
212 	}
213 }