Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport')
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java30
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java257
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java4
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java113
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java69
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java41
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java8
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java51
8 files changed, 427 insertions, 146 deletions
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java
new file mode 100644
index 0000000000..aa4623571d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.util.concurrent.CancellationException;
+
+/**
+ * An exception to report that the user canceled the SSH authentication.
+ */
+public class AuthenticationCanceledException extends CancellationException {
+
+ // If this is not a CancellationException sshd will try other authentication
+ // mechanisms.
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new {@link AuthenticationCanceledException}.
+ */
+ public AuthenticationCanceledException() {
+ super(SshdText.get().authenticationCanceled);
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index 420a1d16eb..0d6f3027f2 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -18,21 +18,30 @@ import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import org.apache.sshd.client.ClientFactoryManager;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.client.session.ClientUserAuthService;
+import org.apache.sshd.common.AttributeRepository;
import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.kex.KexState;
import org.apache.sshd.common.util.Readable;
import org.apache.sshd.common.util.buffer.Buffer;
import org.eclipse.jgit.errors.InvalidPatternException;
@@ -68,6 +77,17 @@ public class JGitClientSession extends ClientSessionImpl {
private volatile StatefulProxyConnector proxyHandler;
/**
+ * Work-around for bug 565394 / SSHD-1050; remove when using sshd 2.6.0.
+ */
+ private volatile AuthFuture authFuture;
+
+ /** Records exceptions before there is an authFuture. */
+ private List<Throwable> earlyErrors = new ArrayList<>();
+
+ /** Guards setting an earlyError and the authFuture together. */
+ private final Object errorLock = new Object();
+
+ /**
* @param manager
* @param session
* @throws Exception
@@ -77,6 +97,125 @@ public class JGitClientSession extends ClientSessionImpl {
super(manager, session);
}
+ // BEGIN Work-around for bug 565394 / SSHD-1050
+ // Remove when using sshd 2.6.0.
+
+ @Override
+ public AuthFuture auth() throws IOException {
+ if (getUsername() == null) {
+ throw new IllegalStateException(
+ SshdText.get().sessionWithoutUsername);
+ }
+ ClientUserAuthService authService = getUserAuthService();
+ String serviceName = nextServiceName();
+ List<Throwable> errors = null;
+ AuthFuture future;
+ // Guard both getting early errors and setting authFuture
+ synchronized (errorLock) {
+ future = authService.auth(serviceName);
+ if (future == null) {
+ // Internal error; no translation.
+ throw new IllegalStateException(
+ "No auth future generated by service '" //$NON-NLS-1$
+ + serviceName + '\'');
+ }
+ errors = earlyErrors;
+ earlyErrors = null;
+ authFuture = future;
+ }
+ if (errors != null && !errors.isEmpty()) {
+ Iterator<Throwable> iter = errors.iterator();
+ Throwable first = iter.next();
+ iter.forEachRemaining(t -> {
+ if (t != first && t != null) {
+ first.addSuppressed(t);
+ }
+ });
+ // Mark the future as having had an exception; just to be on the
+ // safe side. Actually, there shouldn't be anyone waiting on this
+ // future yet.
+ future.setException(first);
+ if (log.isDebugEnabled()) {
+ log.debug("auth({}) early exception type={}: {}", //$NON-NLS-1$
+ this, first.getClass().getSimpleName(),
+ first.getMessage());
+ }
+ if (first instanceof SshException) {
+ throw new SshException(
+ ((SshException) first).getDisconnectCode(),
+ first.getMessage(), first);
+ }
+ throw new IOException(first.getMessage(), first);
+ }
+ return future;
+ }
+
+ @Override
+ protected void signalAuthFailure(AuthFuture future, Throwable t) {
+ signalAuthFailure(t);
+ }
+
+ private void signalAuthFailure(Throwable t) {
+ AuthFuture future = authFuture;
+ if (future == null) {
+ synchronized (errorLock) {
+ if (earlyErrors != null) {
+ earlyErrors.add(t);
+ }
+ future = authFuture;
+ }
+ }
+ if (future != null) {
+ future.setException(t);
+ }
+ if (log.isDebugEnabled()) {
+ boolean signalled = future != null && t == future.getException();
+ log.debug("signalAuthFailure({}) type={}, signalled={}: {}", this, //$NON-NLS-1$
+ t.getClass().getSimpleName(), Boolean.valueOf(signalled),
+ t.getMessage());
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) {
+ signalAuthFailure(t);
+ super.exceptionCaught(t);
+ }
+
+ @Override
+ protected void preClose() {
+ signalAuthFailure(
+ new SshException(SshdText.get().authenticationOnClosedSession));
+ super.preClose();
+ }
+
+ @Override
+ protected void handleDisconnect(int code, String msg, String lang,
+ Buffer buffer) throws Exception {
+ signalAuthFailure(new SshException(code, msg));
+ super.handleDisconnect(code, msg, lang, buffer);
+ }
+
+ @Override
+ protected <C extends Collection<ClientSessionEvent>> C updateCurrentSessionState(
+ C newState) {
+ if (closeFuture.isClosed()) {
+ newState.add(ClientSessionEvent.CLOSED);
+ }
+ if (isAuthenticated()) { // authFuture.isSuccess()
+ newState.add(ClientSessionEvent.AUTHED);
+ }
+ if (KexState.DONE.equals(getKexState())) {
+ AuthFuture future = authFuture;
+ if (future == null || future.isFailure()) {
+ newState.add(ClientSessionEvent.WAIT_AUTH);
+ }
+ }
+ return newState;
+ }
+
+ // END Work-around for bug 565394 / SSHD-1050
+
/**
* Retrieves the {@link HostConfigEntry} this session was created for.
*
@@ -419,4 +558,122 @@ public class JGitClientSession extends ClientSessionImpl {
return b.toString();
}
+ @Override
+ public <T> T getAttribute(AttributeKey<T> key) {
+ T value = super.getAttribute(key);
+ if (value == null) {
+ IoSession ioSession = getIoSession();
+ if (ioSession != null) {
+ Object obj = ioSession.getAttribute(AttributeRepository.class);
+ if (obj instanceof AttributeRepository) {
+ AttributeRepository sessionAttributes = (AttributeRepository) obj;
+ value = sessionAttributes.resolveAttribute(key);
+ }
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public PropertyResolver getParentPropertyResolver() {
+ IoSession ioSession = getIoSession();
+ if (ioSession != null) {
+ Object obj = ioSession.getAttribute(AttributeRepository.class);
+ if (obj instanceof PropertyResolver) {
+ return (PropertyResolver) obj;
+ }
+ }
+ return super.getParentPropertyResolver();
+ }
+
+ /**
+ * An {@link AttributeRepository} that chains together two other attribute
+ * sources in a hierarchy.
+ */
+ public static class ChainingAttributes implements AttributeRepository {
+
+ private final AttributeRepository delegate;
+
+ private final AttributeRepository parent;
+
+ /**
+ * Create a new {@link ChainingAttributes} attribute source.
+ *
+ * @param self
+ * to search for attributes first
+ * @param parent
+ * to search for attributes if not found in {@code self}
+ */
+ public ChainingAttributes(AttributeRepository self,
+ AttributeRepository parent) {
+ this.delegate = self;
+ this.parent = parent;
+ }
+
+ @Override
+ public int getAttributesCount() {
+ return delegate.getAttributesCount();
+ }
+
+ @Override
+ public <T> T getAttribute(AttributeKey<T> key) {
+ return delegate.getAttribute(Objects.requireNonNull(key));
+ }
+
+ @Override
+ public Collection<AttributeKey<?>> attributeKeys() {
+ return delegate.attributeKeys();
+ }
+
+ @Override
+ public <T> T resolveAttribute(AttributeKey<T> key) {
+ T value = getAttribute(Objects.requireNonNull(key));
+ if (value == null) {
+ return parent.getAttribute(key);
+ }
+ return value;
+ }
+ }
+
+ /**
+ * A {@link ChainingAttributes} repository that doubles as a
+ * {@link PropertyResolver}. The property map can be set via the attribute
+ * key {@link SessionAttributes#PROPERTIES}.
+ */
+ public static class SessionAttributes extends ChainingAttributes
+ implements PropertyResolver {
+
+ /** Key for storing a map of properties in the attributes. */
+ public static final AttributeKey<Map<String, Object>> PROPERTIES = new AttributeKey<>();
+
+ private final PropertyResolver parentProperties;
+
+ /**
+ * Creates a new {@link SessionAttributes} attribute and property
+ * source.
+ *
+ * @param self
+ * to search for attributes first
+ * @param parent
+ * to search for attributes if not found in {@code self}
+ * @param parentProperties
+ * to search for properties if not found in {@code self}
+ */
+ public SessionAttributes(AttributeRepository self,
+ AttributeRepository parent, PropertyResolver parentProperties) {
+ super(self, parent);
+ this.parentProperties = parentProperties;
+ }
+
+ @Override
+ public PropertyResolver getParentPropertyResolver() {
+ return parentProperties;
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ Map<String, Object> props = getAttribute(PROPERTIES);
+ return props == null ? Collections.emptyMap() : props;
+ }
+ }
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
index 0a7082cefe..4abd6e901a 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
@@ -9,8 +9,6 @@
*/
package org.eclipse.jgit.internal.transport.sshd;
-import java.util.concurrent.CancellationException;
-
import org.apache.sshd.client.ClientAuthenticationManager;
import org.apache.sshd.client.auth.keyboard.UserInteraction;
import org.apache.sshd.client.auth.password.UserAuthPassword;
@@ -49,7 +47,7 @@ public class JGitPasswordAuthentication extends UserAuthPassword {
}
String password = getPassword(session, interaction);
if (password == null) {
- throw new CancellationException();
+ throw new AuthenticationCanceledException();
}
// sendPassword takes a buffer as first argument, but actually doesn't
// use it and creates its own buffer...
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
index b8dd60fb10..beaaecaac9 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -23,12 +23,16 @@ import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;
+import org.apache.sshd.client.ClientAuthenticationManager;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.future.ConnectFuture;
@@ -45,6 +49,9 @@ import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.ChainingAttributes;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.SessionAttributes;
import org.eclipse.jgit.internal.transport.sshd.proxy.HttpClientConnector;
import org.eclipse.jgit.internal.transport.sshd.proxy.Socks5ClientConnector;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -52,6 +59,7 @@ import org.eclipse.jgit.transport.SshConstants;
import org.eclipse.jgit.transport.sshd.KeyCache;
import org.eclipse.jgit.transport.sshd.ProxyData;
import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
+import org.eclipse.jgit.util.StringUtils;
/**
* Customized {@link SshClient} for JGit. It creates specialized
@@ -75,6 +83,16 @@ public class JGitSshClient extends SshClient {
*/
public static final AttributeKey<String> PREFERRED_AUTHENTICATIONS = new AttributeKey<>();
+ /**
+ * An attribute key for storing an alternate local address to connect to if
+ * a local forward from a ProxyJump ssh config is present. If set,
+ * {@link #connect(HostConfigEntry, AttributeRepository, SocketAddress)}
+ * will not connect to the address obtained from the {@link HostConfigEntry}
+ * but to the address stored in this key (which is assumed to forward the
+ * {@code HostConfigEntry} address).
+ */
+ public static final AttributeKey<SshdSocketAddress> LOCAL_FORWARD_ADDRESS = new AttributeKey<>();
+
private KeyCache keyCache;
private CredentialsProvider credentialsProvider;
@@ -95,40 +113,72 @@ public class JGitSshClient extends SshClient {
throw new IllegalStateException("SshClient not started."); //$NON-NLS-1$
}
Objects.requireNonNull(hostConfig, "No host configuration"); //$NON-NLS-1$
- String host = ValidateUtils.checkNotNullAndNotEmpty(
+ String originalHost = ValidateUtils.checkNotNullAndNotEmpty(
hostConfig.getHostName(), "No target host"); //$NON-NLS-1$
- int port = hostConfig.getPort();
- ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); //$NON-NLS-1$
+ int originalPort = hostConfig.getPort();
+ ValidateUtils.checkTrue(originalPort > 0, "Invalid port: %d", //$NON-NLS-1$
+ originalPort);
+ InetSocketAddress originalAddress = new InetSocketAddress(originalHost,
+ originalPort);
+ InetSocketAddress targetAddress = originalAddress;
String userName = hostConfig.getUsername();
- InetSocketAddress address = new InetSocketAddress(host, port);
- ConnectFuture connectFuture = new DefaultConnectFuture(
- userName + '@' + address, null);
+ String id = userName + '@' + originalAddress;
+ AttributeRepository attributes = chain(context, this);
+ SshdSocketAddress localForward = attributes
+ .resolveAttribute(LOCAL_FORWARD_ADDRESS);
+ if (localForward != null) {
+ targetAddress = new InetSocketAddress(localForward.getHostName(),
+ localForward.getPort());
+ id += '/' + targetAddress.toString();
+ }
+ ConnectFuture connectFuture = new DefaultConnectFuture(id, null);
SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(
- connectFuture, userName, address, hostConfig);
- // sshd needs some entries from the host config already in the
- // constructor of the session. Put those as properties on this client,
- // where it will find them. We can set the host config only once the
- // session object has been created.
- copyProperty(
- hostConfig.getProperty(SshConstants.PREFERRED_AUTHENTICATIONS,
- getAttribute(PREFERRED_AUTHENTICATIONS)),
- PREFERRED_AUTHS);
- setAttribute(HOST_CONFIG_ENTRY, hostConfig);
- setAttribute(ORIGINAL_REMOTE_ADDRESS, address);
+ connectFuture, userName, originalAddress, hostConfig);
+ attributes = sessionAttributes(attributes, hostConfig, originalAddress);
// Proxy support
- ProxyData proxy = getProxyData(address);
- if (proxy != null) {
- address = configureProxy(proxy, address);
- proxy.clearPassword();
+ if (localForward == null) {
+ ProxyData proxy = getProxyData(targetAddress);
+ if (proxy != null) {
+ targetAddress = configureProxy(proxy, targetAddress);
+ proxy.clearPassword();
+ }
}
- connector.connect(address, this, localAddress).addListener(listener);
+ connector.connect(targetAddress, attributes, localAddress)
+ .addListener(listener);
return connectFuture;
}
- private void copyProperty(String value, String key) {
- if (value != null && !value.isEmpty()) {
- getProperties().put(key, value);
+ private AttributeRepository chain(AttributeRepository self,
+ AttributeRepository parent) {
+ if (self == null) {
+ return Objects.requireNonNull(parent);
}
+ if (parent == null || parent == self) {
+ return self;
+ }
+ return new ChainingAttributes(self, parent);
+ }
+
+ private AttributeRepository sessionAttributes(AttributeRepository parent,
+ HostConfigEntry hostConfig, InetSocketAddress originalAddress) {
+ // sshd needs some entries from the host config already in the
+ // constructor of the session. Put those into a dedicated
+ // AttributeRepository for the new session where it will find them.
+ // We can set the host config only once the session object has been
+ // created.
+ Map<AttributeKey<?>, Object> data = new HashMap<>();
+ data.put(HOST_CONFIG_ENTRY, hostConfig);
+ data.put(ORIGINAL_REMOTE_ADDRESS, originalAddress);
+ String preferredAuths = hostConfig.getProperty(
+ SshConstants.PREFERRED_AUTHENTICATIONS,
+ resolveAttribute(PREFERRED_AUTHENTICATIONS));
+ if (!StringUtils.isEmptyOrNull(preferredAuths)) {
+ data.put(SessionAttributes.PROPERTIES,
+ Collections.singletonMap(PREFERRED_AUTHS, preferredAuths));
+ }
+ return new SessionAttributes(
+ AttributeRepository.ofAttributesMap(data),
+ parent, this);
}
private ProxyData getProxyData(InetSocketAddress remoteAddress) {
@@ -219,11 +269,6 @@ public class JGitSshClient extends SshClient {
int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
session.getProperties().put(PASSWORD_PROMPTS,
Integer.valueOf(numberOfPasswordPrompts));
- FilePasswordProvider passwordProvider = getFilePasswordProvider();
- if (passwordProvider instanceof RepeatingFilePasswordProvider) {
- ((RepeatingFilePasswordProvider) passwordProvider)
- .setAttempts(numberOfPasswordPrompts);
- }
List<Path> identities = hostConfig.getIdentities().stream()
.map(s -> {
try {
@@ -237,6 +282,7 @@ public class JGitSshClient extends SshClient {
.collect(Collectors.toList());
CachingKeyPairProvider ourConfiguredKeysProvider = new CachingKeyPairProvider(
identities, keyCache);
+ FilePasswordProvider passwordProvider = getFilePasswordProvider();
ourConfiguredKeysProvider.setPasswordFinder(passwordProvider);
if (hostConfig.isIdentitiesOnly()) {
session.setKeyIdentityProvider(ourConfiguredKeysProvider);
@@ -265,9 +311,7 @@ public class JGitSshClient extends SshClient {
log.warn(format(SshdText.get().configInvalidPositive,
SshConstants.NUMBER_OF_PASSWORD_PROMPTS, prompts));
}
- // Default for NumberOfPasswordPrompts according to
- // https://man.openbsd.org/ssh_config
- return 3;
+ return ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS;
}
/**
@@ -408,6 +452,5 @@ public class JGitSshClient extends SshClient {
};
}
-
}
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
index 5b48a8cf99..078e411f29 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -16,8 +16,12 @@ import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+import org.apache.sshd.client.ClientAuthenticationManager;
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.session.SessionContext;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -25,39 +29,61 @@ import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
/**
- * A bridge from sshd's {@link RepeatingFilePasswordProvider} to our
+ * A bridge from sshd's {@link FilePasswordProvider} to our per-session
* {@link KeyPasswordProvider} API.
*/
-public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
+public class PasswordProviderWrapper implements FilePasswordProvider {
- private final KeyPasswordProvider delegate;
+ private static final AttributeKey<PerSessionState> STATE = new AttributeKey<>();
- private Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+ private static class PerSessionState {
+
+ Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+
+ KeyPasswordProvider delegate;
- /**
- * @param delegate
- */
- public PasswordProviderWrapper(@NonNull KeyPasswordProvider delegate) {
- this.delegate = delegate;
}
- @Override
- public void setAttempts(int numberOfPasswordPrompts) {
- delegate.setAttempts(numberOfPasswordPrompts);
+ private final Supplier<KeyPasswordProvider> factory;
+
+ /**
+ * Creates a new {@link PasswordProviderWrapper}.
+ *
+ * @param factory
+ * to use to create per-session {@link KeyPasswordProvider}s
+ */
+ public PasswordProviderWrapper(
+ @NonNull Supplier<KeyPasswordProvider> factory) {
+ this.factory = factory;
}
- @Override
- public int getAttempts() {
- return delegate.getAttempts();
+ private PerSessionState getState(SessionContext context) {
+ PerSessionState state = context.getAttribute(STATE);
+ if (state == null) {
+ state = new PerSessionState();
+ state.delegate = factory.get();
+ Integer maxNumberOfAttempts = context
+ .getInteger(ClientAuthenticationManager.PASSWORD_PROMPTS);
+ if (maxNumberOfAttempts != null
+ && maxNumberOfAttempts.intValue() > 0) {
+ state.delegate.setAttempts(maxNumberOfAttempts.intValue());
+ } else {
+ state.delegate.setAttempts(
+ ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS);
+ }
+ context.setAttribute(STATE, state);
+ }
+ return state;
}
@Override
public String getPassword(SessionContext session, NamedResource resource,
int attemptIndex) throws IOException {
String key = resource.getName();
- int attempt = counts
+ PerSessionState state = getState(session);
+ int attempt = state.counts
.computeIfAbsent(key, k -> new AtomicInteger()).get();
- char[] passphrase = delegate.getPassphrase(toUri(key), attempt);
+ char[] passphrase = state.delegate.getPassphrase(toUri(key), attempt);
if (passphrase == null) {
return null;
}
@@ -74,18 +100,19 @@ public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
String password, Exception err)
throws IOException, GeneralSecurityException {
String key = resource.getName();
- AtomicInteger count = counts.get(key);
+ PerSessionState state = getState(session);
+ AtomicInteger count = state.counts.get(key);
int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
ResourceDecodeResult result = null;
try {
- if (delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
+ if (state.delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
result = ResourceDecodeResult.RETRY;
} else {
result = ResourceDecodeResult.TERMINATE;
}
} finally {
if (result != ResourceDecodeResult.RETRY) {
- counts.remove(key);
+ state.counts.remove(key);
}
}
return result;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
deleted file mode 100644
index 86f0fe7b60..0000000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.internal.transport.sshd;
-
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
-
-/**
- * A {@link FilePasswordProvider} augmented to support repeatedly asking for
- * passwords.
- *
- */
-public interface RepeatingFilePasswordProvider extends FilePasswordProvider {
-
- /**
- * Define the maximum number of attempts to get a password that should be
- * attempted for one identity resource through this provider.
- *
- * @param numberOfPasswordPrompts
- * number of times to ask for a password;
- * {@link IllegalArgumentException} may be thrown if <= 0
- */
- void setAttempts(int numberOfPasswordPrompts);
-
- /**
- * Gets the maximum number of attempts to get a password that should be
- * attempted for one identity resource through this provider.
- *
- * @return the maximum number of attempts to try, always >= 1.
- */
- default int getAttempts() {
- return 1;
- }
-
-}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
index f67170e407..13bb3ebe75 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -19,12 +19,16 @@ public final class SshdText extends TranslationBundle {
// @formatter:off
/***/ public String authenticationCanceled;
+ /***/ public String authenticationOnClosedSession;
/***/ public String closeListenerFailed;
/***/ public String configInvalidPath;
/***/ public String configInvalidPattern;
/***/ public String configInvalidPositive;
+ /***/ public String configInvalidProxyJump;
/***/ public String configNoKnownHostKeyAlgorithms;
/***/ public String configNoRemainingHostKeyAlgorithms;
+ /***/ public String configProxyJumpNotSsh;
+ /***/ public String configProxyJumpWithPath;
/***/ public String ftpCloseFailed;
/***/ public String gssapiFailure;
/***/ public String gssapiInitFailure;
@@ -57,12 +61,14 @@ public final class SshdText extends TranslationBundle {
/***/ public String knownHostsUnknownKeyType;
/***/ public String knownHostsUserAskCreationMsg;
/***/ public String knownHostsUserAskCreationPrompt;
+ /***/ public String loginDenied;
/***/ public String passwordPrompt;
/***/ public String proxyCannotAuthenticate;
/***/ public String proxyHttpFailure;
/***/ public String proxyHttpInvalidUserName;
/***/ public String proxyHttpUnexpectedReply;
/***/ public String proxyHttpUnspecifiedFailureReason;
+ /***/ public String proxyJumpAbort;
/***/ public String proxyPasswordPrompt;
/***/ public String proxySocksAuthenticationFailed;
/***/ public String proxySocksFailureForbidden;
@@ -87,9 +93,11 @@ public final class SshdText extends TranslationBundle {
/***/ public String serverIdTooLong;
/***/ public String serverIdWithNul;
/***/ public String sessionCloseFailed;
+ /***/ public String sessionWithoutUsername;
/***/ public String sshClosingDown;
/***/ public String sshCommandTimeout;
/***/ public String sshProcessStillRunning;
+ /***/ public String sshProxySessionCloseFailed;
/***/ public String unknownProxyProtocol;
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
index d5b80374cb..0500a63428 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
@@ -13,6 +13,8 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import org.eclipse.jgit.util.HttpSupport;
+
/**
* A basic parser for HTTP response headers. Handles status lines and
* authentication headers (WWW-Authenticate, Proxy-Authenticate).
@@ -135,7 +137,7 @@ public final class HttpParser {
int length = header.length();
for (int i = 0; i < length;) {
int start = skipWhiteSpace(header, i);
- int end = scanToken(header, start);
+ int end = HttpSupport.scanToken(header, start);
if (end <= start) {
break;
}
@@ -156,7 +158,7 @@ public final class HttpParser {
// optional legacy whitespace around the equals sign), where the
// value can be either a token or a quoted string.
start = skipWhiteSpace(header, start);
- int end = scanToken(header, start);
+ int end = HttpSupport.scanToken(header, start);
if (end == start) {
// Nothing found. Either at end or on a comma.
if (start < header.length() && header.charAt(start) == ',') {
@@ -222,7 +224,7 @@ public final class HttpParser {
challenge.addArgument(header.substring(start, end), value);
start = nextEnd[0];
} else {
- int nextEnd = scanToken(header, nextStart);
+ int nextEnd = HttpSupport.scanToken(header, nextStart);
challenge.addArgument(header.substring(start, end),
header.substring(nextStart, nextEnd));
start = nextEnd;
@@ -244,49 +246,6 @@ public final class HttpParser {
return i;
}
- private static int scanToken(String header, int from) {
- int length = header.length();
- int i = from;
- while (i < length) {
- char c = header.charAt(i);
- switch (c) {
- case '!':
- case '#':
- case '$':
- case '%':
- case '&':
- case '\'':
- case '*':
- case '+':
- case '-':
- case '.':
- case '^':
- case '_':
- case '`':
- case '|':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- i++;
- break;
- default:
- if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
- i++;
- break;
- }
- return i;
- }
- }
- return i;
- }
-
private static String scanQuotedString(String header, int from, int[] to) {
StringBuilder result = new StringBuilder();
int length = header.length();

Back to the top