1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
## Event listeners
The code supports registering many types of event listeners that enable receiving notifications about important events
as well as sometimes intervening in the way these events are handled. All listener interfaces extend `SshdEventListener`
so they can be easily detected and distinguished from other `EventListener`(s).
In general, event listeners are **cumulative** - e.g., any channel event listeners registered on the `SshClient/Server` are
automatically added to all sessions, *in addition* to any such listeners registered on the `Session`, as well as any specific
listeners registered on a specific `Channel` - e.g.,
```java
// Any channel event will be signalled to ALL the registered listeners
sshClient/Server.addChannelListener(new Listener1());
sshClient/Server.addSessionListener(new SessionListener() {
@Override
public void sessionCreated(Session session) {
session.addChannelListener(new Listener2());
session.addChannelListener(new ChannelListener() {
@Override
public void channelInitialized(Channel channel) {
channel.addChannelListener(new Listener3());
}
});
}
});
```
### `IoServiceEventListener`
This listener provides low-level events regarding connection establishment (by the client) or acceptance (by the server). The listener is registered
on the `IoServiceFactory` via the `FactoryManager`-s (i.e., `SshClient/Server#setIoServiceEventListener`). Unlike other listeners defined in this
section, it is **not cumulative** - i.e., one can `setIoServiceEventListener` but not `addIoServiceEventListener` - thus **replacing** any previously
registered listener.
### `SessionListener`
Informs about session related events. One can modify the session - although the modification effect depends on the session's **state**. E.g., if one
changes the ciphers *after* the key exchange (KEX) phase, then they will take effect only if the keys are re-negotiated. Furthermore, invoking some
session API(s) - event `getSomeValue` at the wrong time might yield unexpected results. It is important to read the documentation very carefully and
understand at which stage each listener method is invoked, what are the limitations and what are the repercussions of changes at that stage.
In this context, it is worth mentioning that one can attach to sessions **arbitrary attributes** that can be retrieved by the user's code later on:
```java
public static final AttributeKey<String> STR_KEY = new AttributeKey<>();
public static final AttributeKey<Long> LONG_KEY = new AttributeKey<>();
sshClient/Server.addSessionListener(new SessionListener() {
@Override
public void sessionEstablished(Session session) {
// examine the peer address or the connection context and set some attributes
}
@Override
public void sessionCreated(Session session) {
session.setAttribute(STR_KEY, "Some string value");
session.setAttribute(LONG_KEY, 3777347L);
// ...etc...
}
@Override
public void sessionClosed(Session session) {
String str = session.getAttribute(STR_KEY);
Long l = session.getAttribute(LONG_KEY);
// ... do something with the retrieved attributes ...
}
});
```
The attributes cache is automatically cleared once the session is closed.
### `ChannelListener`
Informs about channel related events - as with sessions, once can influence the channel to some extent, depending on the channel's **state**.
The ability to influence channels is much more limited than sessions. In this context, it is worth mentioning that one can attach to channels
**arbitrary attributes** that can be retrieved by the user's code later on and are cleared when channel is closed - same was as it is done for sessions.
### `UnknownChannelReferenceHandler`
Invoked whenever a message intended for an unknown channel is received. By default, the code **ignores** the vast majority of such messages
and logs them at DEBUG level. For a select few types of messages the code generates an `SSH_CHANNEL_MSG_FAILURE` packet that is sent to the
peer session - see `DefaultUnknownChannelReferenceHandler` implementation. The user may register handlers at any level - client/server, session
and/or connection service - the one registered "closest" to connection service will be used.
An **experimental** `ChannelIdTrackingUnknownChannelReferenceHandler` is available in _sshd-contrib_ package that applies the "leniency" of
the `DefaultUnknownChannelReferenceHandler` only if the unknown channel is one that has been assigned in the past - otherwise it throws an
exception. In order to use it, the handler instance needs to be registered as **both** an `UnknownChannelReferenceHandler` and a `ChannelListener`.
### `KexExtensionHandler`
Provides hooks for implementing [KEX extension negotiation](https://tools.ietf.org/html/rfc8308).
**Note:** it can be used for monitoring the KEX mechanism and intervene in a more general case for other purposes as well. In any case, it is
highly recommended though to read the interface documentation and also review the code that invokes it before attempting to use it.
An **experimental** implementation example is available for the client side - see `DefaultClientKexExtensionHandler`.
### `ReservedSessionMessagesHandler`
Can be used to handle the following cases:
* Intervene during the initial identification and KEX
* [SSH_MSG_IGNORE](https://tools.ietf.org/html/rfc4253#section-11.2)
* [SSH_MSG_DEBUG](https://tools.ietf.org/html/rfc4253#section-11.3)
* [SSH_MSG_UNIMPLEMENTED](https://tools.ietf.org/html/rfc4253#section-11.4)
* Implementing a custom session heartbeat mechanism - for **both**
[client](./client-setup.md#keeping-the-session-alive-while-no-traffic)
or [server](./server-setup.md#providing-server-side-heartbeat).
* Any other unrecognized message received in the session.
**Note:** The `handleUnimplementedMessage` method serves both for handling `SSH_MSG_UNIMPLEMENTED` and any other unrecognized
message received in the session as well.
```java
class MyClientSideReservedSessionMessagesHandler implements ReservedSessionMessagesHandler {
@Override
public boolean handleUnimplementedMessage(Session session, int cmd, Buffer buffer) throws Exception {
switch(cmd) {
case MY_SPECIAL_CMD1:
....
return true;
case MY_SPECIAL_CMD2:
....
return true;
default:
return false; // send SSH_MSG_UNIMPLEMENTED reply if necessary
}
}
}
// client side
SshClient client = SshClient.setUpDefaultClient();
// This is the default for ALL sessions unless specifically overridden
client.setReservedSessionMessagesHandler(new MyClientSideReservedSessionMessagesHandler());
// Adding it via a session listener
client.setSessionListener(new SessionListener() {
@Override
public void sessionCreated(Session session) {
// Overrides the one set at the client level.
if (isSomeSessionOfInterest(session)) {
session.setReservedSessionMessagesHandler(new MyClientSessionReservedSessionMessagesHandler(session));
}
}
});
try (ClientSession session = client.connect(user, host, port).verify(...timeout...).getSession()) {
// setting it explicitly
session.setReservedSessionMessagesHandler(new MyOtherClientSessionReservedSessionMessagesHandler(session));
session.addPasswordIdentity(password);
session.auth().verify(...timeout...);
...use the session...
}
// server side
SshServer server = SshServer.setUpDefaultServer();
// This is the default for ALL sessions unless specifically overridden
server.setReservedSessionMessagesHandler(new MyServerSideReservedSessionMessagesHandler());
// Adding it via a session listener
server.setSessionListener(new SessionListener() {
@Override
public void sessionCreated(Session session) {
// Overrides the one set at the server level.
if (isSomeSessionOfInterest(session)) {
session.setReservedSessionMessagesHandler(new MyServerSessionReservedSessionMessagesHandler(session));
}
}
});
```
**NOTE:** Unlike "regular" event listeners, the handler is not cumulative - i.e., setting it overrides the previous instance
rather than being accumulated. However, one can use the `EventListenerUtils` and create a cumulative listener - see how
`SessionListener` or `ChannelListener` proxies were implemented.
### `SessionDisconnectHandler`
This handler can be registered in order to monitor session disconnect initiated by the internal code due to various
protocol requirements - e.g., unknown service, idle timeout, etc.. In many cases the implementor can intervene and
cancel the disconnect by handling the problem somehow and then signaling to the code that there is no longer any need
to disconnect. The handler can be registered globally at the `SshClient/Server` instance or per-session (via a `SessionListener`).
**NOTE(s):**
* This handler is non-cumulative - i.e., setting it replaces any existing previous handler instance.
* If any exception is thrown from one of the invoked callback methods then session disconnect proceeds as if
the handler decided not to intervene.
### `SignalListener`
Informs about signal requests as described in [RFC 4254 - section 6.9](https://tools.ietf.org/html/rfc4254#section-6.9), "break" requests
(sent as SIGINT) as described in [RFC 4335](https://tools.ietf.org/html/rfc4335) and "window-change" (sent as SIGWINCH) requests as described
in [RFC 4254 - section 6.7](https://tools.ietf.org/html/rfc4254#section-6.7)
### `PasswordAuthenticationReporter`
Used to inform about the progress of the client-side password based authentication as described in [RFC-4252 section 8](https://tools.ietf.org/html/rfc4252#section-8).
Can be registered globally on the `SshClient` and also for a specific `ClientSession` after it is established but before its `auth()` method is called - thus
overriding any globally registered instance.
### `PublicKeyAuthenticationReporter`
Used to inform about the progress of the client-side public key authentication as described in [RFC-4252 section 7](https://tools.ietf.org/html/rfc4252#section-7).
Can be registered globally on the `SshClient` and also for a specific `ClientSession` after it is established but before its `auth()` method is called - thus
overriding any globally registered instance.
### `HostBasedAuthenticationReporter`
Used to inform about the progress of the client-side host-based authentication as described in [RFC-4252 section 9](https://tools.ietf.org/html/rfc4252#section-9).
Can be registered globally on the `SshClient` and also for a specific `ClientSession` after it is established but before its `auth()` method is called - thus
overriding any globally registered instance.
|