|
18 | 18 | */
|
19 | 19 | package org.apache.sshd.client.channel;
|
20 | 20 |
|
| 21 | +import java.io.EOFException; |
21 | 22 | import java.io.IOException;
|
22 | 23 | import java.util.Collections;
|
| 24 | +import java.util.Locale; |
23 | 25 | import java.util.Map;
|
| 26 | +import java.util.concurrent.ScheduledExecutorService; |
| 27 | +import java.util.concurrent.ScheduledFuture; |
| 28 | +import java.util.concurrent.TimeUnit; |
| 29 | +import java.util.concurrent.atomic.AtomicReference; |
24 | 30 |
|
| 31 | +import org.apache.sshd.common.FactoryManager; |
25 | 32 | import org.apache.sshd.common.SshConstants;
|
26 | 33 | import org.apache.sshd.common.channel.PtyChannelConfiguration;
|
27 | 34 | import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
|
28 | 35 | import org.apache.sshd.common.channel.PtyChannelConfigurationMutator;
|
29 | 36 | import org.apache.sshd.common.channel.PtyMode;
|
| 37 | +import org.apache.sshd.common.io.AbstractIoWriteFuture; |
| 38 | +import org.apache.sshd.common.io.IoWriteFuture; |
30 | 39 | import org.apache.sshd.common.session.Session;
|
31 | 40 | import org.apache.sshd.common.util.GenericUtils;
|
32 | 41 | import org.apache.sshd.common.util.MapEntryUtils;
|
33 | 42 | import org.apache.sshd.common.util.buffer.Buffer;
|
34 | 43 | import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
|
35 | 44 | import org.apache.sshd.core.CoreModuleProperties;
|
36 | 45 |
|
| 46 | +import static org.apache.sshd.common.SshConstants.SSH_MSG_PING; |
| 47 | +import static org.apache.sshd.core.CoreModuleProperties.OBFUSCATE_KEYSTROKE_TIMING; |
| 48 | + |
37 | 49 | /**
|
38 | 50 | * <P>
|
39 | 51 | * Serves as the base channel session for executing remote commands - including a full shell. <B>Note:</B> all the
|
|
80 | 92 | * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a>
|
81 | 93 | */
|
82 | 94 | public class PtyCapableChannelSession extends ChannelSession implements PtyChannelConfigurationMutator {
|
| 95 | + private static final String PING_MESSAGE = "PING!"; |
83 | 96 | private boolean agentForwarding;
|
84 | 97 | private boolean usePty;
|
| 98 | + private int obfuscate; |
85 | 99 | private final PtyChannelConfiguration config;
|
| 100 | + private final AtomicReference<ScheduledFuture<?>> chaffFuture = new AtomicReference<>(); |
86 | 101 |
|
87 | 102 | public PtyCapableChannelSession(boolean usePty, PtyChannelConfigurationHolder configHolder, Map<String, ?> env) {
|
88 | 103 | this.usePty = usePty;
|
@@ -267,8 +282,62 @@ protected void doOpenPty() throws IOException {
|
267 | 282 | modes.putByte(PtyMode.TTY_OP_END);
|
268 | 283 | buffer.putBytes(modes.getCompactData());
|
269 | 284 | writePacket(buffer);
|
| 285 | + |
| 286 | + String obf |
| 287 | + = OBFUSCATE_KEYSTROKE_TIMING.get(getSession()).orElse(Boolean.FALSE.toString()).toLowerCase(Locale.ENGLISH); |
| 288 | + if (obf.equals("yes") || obf.equals("true")) { |
| 289 | + obfuscate = 20; |
| 290 | + } else if (obf.equals("no") || obf.equals("false")) { |
| 291 | + obfuscate = 0; |
| 292 | + } else if (obf.matches("interval:[0-9]{1,5}")) { |
| 293 | + obfuscate = Integer.parseInt(obf.substring("interval:".length())); |
| 294 | + } else { |
| 295 | + log.warn("doOpenPty({}) unrecognized value {} for property {}", this, obf, |
| 296 | + OBFUSCATE_KEYSTROKE_TIMING.getName()); |
| 297 | + } |
270 | 298 | }
|
271 | 299 |
|
272 | 300 | sendEnvVariables(session);
|
273 | 301 | }
|
| 302 | + |
| 303 | + @Override |
| 304 | + public IoWriteFuture writePacket(Buffer buffer) throws IOException { |
| 305 | + if (obfuscate > 0 && buffer.available() < 256) { |
| 306 | + log.info("Sending: "); |
| 307 | + if (mayWrite()) { |
| 308 | + Session s = getSession(); |
| 309 | + return s.writePacket(buffer); |
| 310 | + } |
| 311 | + if (log.isDebugEnabled()) { |
| 312 | + log.debug("writePacket({}) Discarding output packet because channel state={}", this, state); |
| 313 | + } |
| 314 | + return AbstractIoWriteFuture.fulfilled(toString(), new EOFException("Channel is being closed")); |
| 315 | + } else { |
| 316 | + return super.writePacket(buffer); |
| 317 | + } |
| 318 | + } |
| 319 | + |
| 320 | + protected void scheduleChaff() { |
| 321 | + FactoryManager manager = getSession().getFactoryManager(); |
| 322 | + ScheduledExecutorService service = manager.getScheduledExecutorService(); |
| 323 | + long delay = 1024 + manager.getRandomFactory().get().random(2048); |
| 324 | + ScheduledFuture<?> future = service.schedule(this::sendChaff, delay, TimeUnit.MILLISECONDS); |
| 325 | + future = this.chaffFuture.getAndSet(future); |
| 326 | + if (future != null) { |
| 327 | + future.cancel(false); |
| 328 | + } |
| 329 | + } |
| 330 | + |
| 331 | + protected void sendChaff() { |
| 332 | + try { |
| 333 | + Buffer buf = getSession().createBuffer(SSH_MSG_PING, PING_MESSAGE.length() + Integer.SIZE); |
| 334 | + buf.putString(PING_MESSAGE); |
| 335 | + getSession().writePacket(buf); |
| 336 | + } catch (IOException e) { |
| 337 | + if (log.isDebugEnabled()) { |
| 338 | + log.debug("Error sending chaff message", e); |
| 339 | + } |
| 340 | + } |
| 341 | + } |
| 342 | + |
274 | 343 | }
|
0 commit comments