1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.internal.transport.sshd;
11
12 import static java.text.MessageFormat.format;
13 import static org.apache.sshd.core.CoreModuleProperties.MAX_IDENTIFICATION_SIZE;
14
15 import java.io.IOException;
16 import java.io.StreamCorruptedException;
17 import java.net.SocketAddress;
18 import java.nio.charset.StandardCharsets;
19 import java.security.GeneralSecurityException;
20 import java.security.PublicKey;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Set;
30
31 import org.apache.sshd.client.ClientFactoryManager;
32 import org.apache.sshd.client.config.hosts.HostConfigEntry;
33 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
34 import org.apache.sshd.client.session.ClientSessionImpl;
35 import org.apache.sshd.common.AttributeRepository;
36 import org.apache.sshd.common.FactoryManager;
37 import org.apache.sshd.common.PropertyResolver;
38 import org.apache.sshd.common.config.keys.KeyUtils;
39 import org.apache.sshd.common.io.IoSession;
40 import org.apache.sshd.common.io.IoWriteFuture;
41 import org.apache.sshd.common.util.Readable;
42 import org.apache.sshd.common.util.buffer.Buffer;
43 import org.eclipse.jgit.errors.InvalidPatternException;
44 import org.eclipse.jgit.fnmatch.FileNameMatcher;
45 import org.eclipse.jgit.internal.transport.sshd.proxy.StatefulProxyConnector;
46 import org.eclipse.jgit.transport.CredentialsProvider;
47 import org.eclipse.jgit.transport.SshConstants;
48
49
50
51
52
53
54
55
56
57
58 public class JGitClientSession extends ClientSessionImpl {
59
60
61
62
63
64
65
66
67
68 private static final int DEFAULT_MAX_IDENTIFICATION_SIZE = 64 * 1024;
69
70 private HostConfigEntry hostConfig;
71
72 private CredentialsProvider credentialsProvider;
73
74 private volatile StatefulProxyConnector proxyHandler;
75
76
77
78
79
80
81 public JGitClientSession(ClientFactoryManager manager, IoSession session)
82 throws Exception {
83 super(manager, session);
84 }
85
86
87
88
89
90
91 public HostConfigEntry getHostConfigEntry() {
92 return hostConfig;
93 }
94
95
96
97
98
99
100
101 public void setHostConfigEntry(HostConfigEntry hostConfig) {
102 this.hostConfig = hostConfig;
103 }
104
105
106
107
108
109
110
111 public void setCredentialsProvider(CredentialsProvider provider) {
112 credentialsProvider = provider;
113 }
114
115
116
117
118
119
120 public CredentialsProvider getCredentialsProvider() {
121 return credentialsProvider;
122 }
123
124
125
126
127
128
129
130
131 public void setProxyHandler(StatefulProxyConnector handler) {
132 proxyHandler = handler;
133 }
134
135 @Override
136 protected IoWriteFuture sendIdentification(String ident)
137 throws IOException {
138 StatefulProxyConnector proxy = proxyHandler;
139 if (proxy != null) {
140 try {
141
142
143
144 proxy.runWhenDone(() -> {
145 JGitClientSession.super.sendIdentification(ident);
146 return null;
147 });
148
149
150 return null;
151 } catch (IOException e) {
152 throw e;
153 } catch (Exception other) {
154 throw new IOException(other.getLocalizedMessage(), other);
155 }
156 }
157 return super.sendIdentification(ident);
158 }
159
160 @Override
161 protected byte[] sendKexInit()
162 throws IOException, GeneralSecurityException {
163 StatefulProxyConnector proxy = proxyHandler;
164 if (proxy != null) {
165 try {
166
167
168
169 proxy.runWhenDone(() -> {
170 JGitClientSession.super.sendKexInit();
171 return null;
172 });
173
174
175 return null;
176 } catch (IOException | GeneralSecurityException e) {
177 throw e;
178 } catch (Exception other) {
179 throw new IOException(other.getLocalizedMessage(), other);
180 }
181 }
182 return super.sendKexInit();
183 }
184
185
186
187
188
189
190
191 @Override
192 public void messageReceived(Readable buffer) throws Exception {
193 StatefulProxyConnector proxy = proxyHandler;
194 if (proxy != null) {
195 proxy.messageReceived(getIoSession(), buffer);
196 } else {
197 super.messageReceived(buffer);
198 }
199 }
200
201 @Override
202 protected String resolveAvailableSignaturesProposal(
203 FactoryManager manager) {
204 Set<String> defaultSignatures = new LinkedHashSet<>();
205 defaultSignatures.addAll(getSignatureFactoriesNames());
206 HostConfigEntry config = resolveAttribute(
207 JGitSshClient.HOST_CONFIG_ENTRY);
208 String hostKeyAlgorithms = config
209 .getProperty(SshConstants.HOST_KEY_ALGORITHMS);
210 if (hostKeyAlgorithms != null && !hostKeyAlgorithms.isEmpty()) {
211 char first = hostKeyAlgorithms.charAt(0);
212 switch (first) {
213 case '+':
214
215
216
217 return String.join(",", defaultSignatures);
218 case '-':
219
220 removeFromList(defaultSignatures,
221 SshConstants.HOST_KEY_ALGORITHMS,
222 hostKeyAlgorithms.substring(1));
223 if (defaultSignatures.isEmpty()) {
224
225
226 log.warn(format(
227 SshdText.get().configNoRemainingHostKeyAlgorithms,
228 hostKeyAlgorithms));
229 }
230 return String.join(",", defaultSignatures);
231 default:
232
233
234 List<String> newNames = filteredList(defaultSignatures,
235 hostKeyAlgorithms);
236 if (newNames.isEmpty()) {
237 log.warn(format(
238 SshdText.get().configNoKnownHostKeyAlgorithms,
239 hostKeyAlgorithms));
240
241 } else {
242 return String.join(",", newNames);
243 }
244 break;
245 }
246 }
247
248
249 ServerKeyVerifier verifier = getServerKeyVerifier();
250 if (verifier instanceof ServerKeyLookup) {
251 SocketAddress remoteAddress = resolvePeerAddress(
252 resolveAttribute(JGitSshClient.ORIGINAL_REMOTE_ADDRESS));
253 List<PublicKey> allKnownKeys = ((ServerKeyLookup) verifier)
254 .lookup(this, remoteAddress);
255 Set<String> reordered = new LinkedHashSet<>();
256 for (PublicKey key : allKnownKeys) {
257 if (key != null) {
258 String keyType = KeyUtils.getKeyType(key);
259 if (keyType != null) {
260 reordered.add(keyType);
261 }
262 }
263 }
264 reordered.addAll(defaultSignatures);
265 return String.join(",", reordered);
266 }
267 return String.join(",", defaultSignatures);
268 }
269
270 private void removeFromList(Set<String> current, String key,
271 String patterns) {
272 for (String toRemove : patterns.split("\\s*,\\s*")) {
273 if (toRemove.indexOf('*') < 0 && toRemove.indexOf('?') < 0) {
274 current.remove(toRemove);
275 continue;
276 }
277 try {
278 FileNameMatcher matcher = new FileNameMatcher(toRemove, null);
279 for (Iterator<String> i = current.iterator(); i.hasNext();) {
280 matcher.reset();
281 matcher.append(i.next());
282 if (matcher.isMatch()) {
283 i.remove();
284 }
285 }
286 } catch (InvalidPatternException e) {
287 log.warn(format(SshdText.get().configInvalidPattern, key,
288 toRemove));
289 }
290 }
291 }
292
293 private List<String> filteredList(Set<String> known, String values) {
294 List<String> newNames = new ArrayList<>();
295 for (String newValue : values.split("\\s*,\\s*")) {
296 if (known.contains(newValue)) {
297 newNames.add(newValue);
298 }
299 }
300 return newNames;
301 }
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323 @Override
324 protected List<String> doReadIdentification(Buffer buffer, boolean server)
325 throws StreamCorruptedException {
326 if (server) {
327
328 throw new IllegalStateException(
329 "doReadIdentification of client called with server=true");
330 }
331 Integer maxIdentLength = MAX_IDENTIFICATION_SIZE.get(this).orElse(null);
332 int maxIdentSize;
333 if (maxIdentLength == null || maxIdentLength
334 .intValue() < DEFAULT_MAX_IDENTIFICATION_SIZE) {
335 maxIdentSize = DEFAULT_MAX_IDENTIFICATION_SIZE;
336 MAX_IDENTIFICATION_SIZE.set(this, Integer.valueOf(maxIdentSize));
337 } else {
338 maxIdentSize = maxIdentLength.intValue();
339 }
340 int current = buffer.rpos();
341 int end = current + buffer.available();
342 if (current >= end) {
343 return null;
344 }
345 byte[] raw = buffer.array();
346 List<String> ident = new ArrayList<>();
347 int start = current;
348 boolean hasNul = false;
349 for (int i = current; i < end; i++) {
350 switch (raw[i]) {
351 case 0:
352 hasNul = true;
353 break;
354 case '\n':
355 int eol = 1;
356 if (i > start && raw[i - 1] == '\r') {
357 eol++;
358 }
359 String line = new String(raw, start, i + 1 - eol - start,
360 StandardCharsets.UTF_8);
361 start = i + 1;
362 if (log.isDebugEnabled()) {
363 log.debug(format("doReadIdentification({0}) line: ", this) +
364 escapeControls(line));
365 }
366 ident.add(line);
367 if (line.startsWith("SSH-")) {
368 if (hasNul) {
369 throw new StreamCorruptedException(
370 format(SshdText.get().serverIdWithNul,
371 escapeControls(line)));
372 }
373 if (line.length() + eol > 255) {
374 throw new StreamCorruptedException(
375 format(SshdText.get().serverIdTooLong,
376 escapeControls(line)));
377 }
378 buffer.rpos(start);
379 return ident;
380 }
381
382
383
384 hasNul = false;
385 break;
386 default:
387 break;
388 }
389 if (i - current + 1 >= maxIdentSize) {
390 String msg = format(SshdText.get().serverIdNotReceived,
391 Integer.toString(maxIdentSize));
392 if (log.isDebugEnabled()) {
393 log.debug(msg);
394 log.debug(buffer.toHex());
395 }
396 throw new StreamCorruptedException(msg);
397 }
398 }
399
400 return null;
401 }
402
403 private static String escapeControls(String s) {
404 StringBuilder b = new StringBuilder();
405 int l = s.length();
406 for (int i = 0; i < l; i++) {
407 char ch = s.charAt(i);
408 if (Character.isISOControl(ch)) {
409 b.append(ch <= 0xF ? "\\u000" : "\\u00")
410 .append(Integer.toHexString(ch));
411 } else {
412 b.append(ch);
413 }
414 }
415 return b.toString();
416 }
417
418 @Override
419 public <T> T getAttribute(AttributeKey<T> key) {
420 T value = super.getAttribute(key);
421 if (value == null) {
422 IoSession ioSession = getIoSession();
423 if (ioSession != null) {
424 Object obj = ioSession.getAttribute(AttributeRepository.class);
425 if (obj instanceof AttributeRepository) {
426 AttributeRepository sessionAttributes = (AttributeRepository) obj;
427 value = sessionAttributes.resolveAttribute(key);
428 }
429 }
430 }
431 return value;
432 }
433
434 @Override
435 public PropertyResolver getParentPropertyResolver() {
436 IoSession ioSession = getIoSession();
437 if (ioSession != null) {
438 Object obj = ioSession.getAttribute(AttributeRepository.class);
439 if (obj instanceof PropertyResolver) {
440 return (PropertyResolver) obj;
441 }
442 }
443 return super.getParentPropertyResolver();
444 }
445
446
447
448
449
450 public static class ChainingAttributes implements AttributeRepository {
451
452 private final AttributeRepository delegate;
453
454 private final AttributeRepository parent;
455
456
457
458
459
460
461
462
463
464 public ChainingAttributes(AttributeRepository self,
465 AttributeRepository parent) {
466 this.delegate = self;
467 this.parent = parent;
468 }
469
470 @Override
471 public int getAttributesCount() {
472 return delegate.getAttributesCount();
473 }
474
475 @Override
476 public <T> T getAttribute(AttributeKey<T> key) {
477 return delegate.getAttribute(Objects.requireNonNull(key));
478 }
479
480 @Override
481 public Collection<AttributeKey<?>> attributeKeys() {
482 return delegate.attributeKeys();
483 }
484
485 @Override
486 public <T> T resolveAttribute(AttributeKey<T> key) {
487 T value = getAttribute(Objects.requireNonNull(key));
488 if (value == null) {
489 return parent.getAttribute(key);
490 }
491 return value;
492 }
493 }
494
495
496
497
498
499
500 public static class SessionAttributes extends ChainingAttributes
501 implements PropertyResolver {
502
503
504 public static final AttributeKey<Map<String, Object>> PROPERTIES = new AttributeKey<>();
505
506 private final PropertyResolver parentProperties;
507
508
509
510
511
512
513
514
515
516
517
518
519 public SessionAttributes(AttributeRepository self,
520 AttributeRepository parent, PropertyResolver parentProperties) {
521 super(self, parent);
522 this.parentProperties = parentProperties;
523 }
524
525 @Override
526 public PropertyResolver getParentPropertyResolver() {
527 return parentProperties;
528 }
529
530 @Override
531 public Map<String, Object> getProperties() {
532 Map<String, Object> props = getAttribute(PROPERTIES);
533 return props == null ? Collections.emptyMap() : props;
534 }
535 }
536 }