diff options
Diffstat (limited to 'org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport')
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(); |