File: commands.md

package info (click to toggle)
libmina-sshd-java 2.13.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 15,428 kB
  • sloc: java: 136,607; xml: 4,544; sh: 917; python: 239; makefile: 2
file content (178 lines) | stat: -rw-r--r-- 8,710 bytes parent folder | download
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
# Commands infrastucture

## `FileSystemFactory` usage

This interface is used to provide "file"-related services/commands - e.g., SCP and SFTP - although it can be used for remote command execution
as well (see the section about commands and the `Aware` interfaces). The default implementation is a `NativeFileSystemFactory`
that simply exposes the [FileSystems.getDefault()](https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileSystems.html#getDefault)
result. However, for "sandboxed" implementations one can use the `VirtualFileSystemFactory`. This implementation provides a way for
deciding what is the logged-in user's file system view and then use a `RootedFileSystemProvider` in order to provide a "sandboxed"
file system where the logged-in user can access only the files under the specified root and no others.

```java
SshServer sshd = SshServer.setUpDefaultServer();
sshd.setFileSystemFactory(new VirtualFileSystemFactory() {
    @Override
    public Path getUserHomeDir(SessionContext session) throws IOException {
        ...use whatever information ...
        return somePath;
    }
});

```

The usage of a `FileSystemFactory` is not limited though to the server only - the `ScpClient` implementation also uses
it in order to retrieve the *local* path for upload/download-ing files/folders. This means that the client side can also
be tailored to present different views for different clients. A special "empty" `NoneFileSystemFactory` is provided in case
no files are expected to be accessed by the server.

## `ExecutorService`-s

The framework requires from time to time spawning some threads in order to function correctly - e.g., commands, SFTP subsystem,
port forwarding (among others) require such support. By default, the framework will allocate an [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html)
for each specific purpose and then shut it down when the module has completed its work - e.g., session was closed. Note that
SSHD uses the `CloseableExecutorService` interface instead of the usual `ExecutorService` in order to provide graceful shutdown.
Users may provide their own `CloseableExecutorService`(s) instead of the internally auto-allocated ones - e.g., in order to
control the max. spawned threads, stack size, track threads, etc... but they can leverage the `SshThreadPoolExecutor` implementation
which should cover most use cases.

Users who want to provide their own `ExecutorService` and not use `SshThreadPoolExecutor` should wrap it as a `NoCloseExecutor`
and take care of shutting it down when SSHD is done with (provided, of course, that the user's own code does not need it to
remain active afterwards...).

```java
/*
 * An example user-provided executor service for SFTP - there are other such locations.
 * By default, the SftpSubsystem implementation creates a single-threaded executor
 * for each session, uses it to spawn the SFTP command handler and shuts
 * it down when the command is destroyed
 */
SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
    .withExecutorServiceProvider(() -> new NoCloseExecutor(mySuperDuperExecutorService))
    .build();
SshServer sshd = SshServer.setUpDefaultServer();
sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(factory));

```

If a single `CloseableExecutorService` is shared between several services, it needs to be wrapped with the
`ThreadUtils.noClose(executor)` method.

```java
CloseableExecutorService sharedService = ...obtain/create an instance...;

SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
    .withExecutorServiceProvider(() -> ThreadUtils.noClose(sharedService))
    .build();

ChannelAgentForwarding forward = new ChannelAgentForwarding(ThreadUtils.noClose(sharedService));

```

**Note:** Do not share the instance returned by `ThreadUtils.noClose` between services as it interferes with
the graceful closing mechanism. Use a new wrapper instance for each service.

## Remote command execution

All command execution - be it shell or single command - boils down to a `Command` instance being created, initialized and then
started. In this context, it is **crucial** to notice that the command's `start()` method implementation **must spawn a new thread** - even
for the simplest or most trivial command. Any attempt to communicate via the established session will most likely **fail** since
the packets processing thread may be blocked by this call. **Note:** one might get away with executing some command in the
context of the thread that called the `start()` method, but it is **extremely dangerous** and should not be attempted.

The command execution code can communicate with the peer client via the input/output/error streams that are provided as
part of the command initialization process. Once the command is done, it should call the `ExitCallback#onExit` method to indicate
that it has finished. The framework will then take care of propagating the exit code, closing the session and (eventually) `destroy()`-ing
the command. **Note**: the command may not assume that it is done until its `destroy()` method is called - i.e., it should not
release or null-ify any of its internal state even if `onExit()` was called.

Upon calling the `onExit` method the code sends an [SSH_MSG_CHANNEL_EOF](https://tools.ietf.org/html/rfc4254#section-5.3) message,
and the provided result status code is sent as an `exit-status` message as described in [RFC4254 - section 6.10](https://tools.ietf.org/html/rfc4254#section-6.10).
The provided message is simply logged at DEBUG level.

```java
// A simple command implementation example
class MyCommand implements Command, Runnable {
    private InputStream in;
    private OutputStream out, err;
    private ExitCallback callback;

    public MyCommand() {
        super();
    }

    @Override
    public void setInputStream(InputStream in) {
        this.in = in;
    }

    @Override
    public void setOutputStream(OutputStream out) {
        this.out = out;
    }

    @Override
    public void setErrorStream(OutputStream err) {
        this.err = err;
    }

    @Override
    public void setExitCallback(ExitCallback callback) {
        this.callback = callback;
    }

    @Override
    public void start(Environment env) throws IOException {
        spawnHandlerThread(this);
    }

    @Override
    public void run() {
        while(true) {
            try {
                String cmd = readCommand(in);
                if ("exit".equals(cmd)) {
                    break;
                }

                handleCommand(cmd, out);
            } catch (Exception e) {
                writeError(err, e);
                callback.onExit(-1, e.getMessage());
                return;
        }

        callback.onExit(0);
    }

    @Override
    public void destroy() throws Exception {
        ...release any allocated resources...
    }
}

```

### `Aware` interfaces

Once created, the `Command` instance is checked to see if it implements one of the `Aware` interfaces that enables
injecting some dynamic data before the command is `start()`-ed.

* `SessionAware` - Injects the `Session` instance through which the command request was received.

* `ChannelSessionAware` - Injects the `ChannelSession` instance through which the command request was received.

* `FileSystemAware` - Injects the result of consulting the `FileSystemFactory` as to the [FileSystem](https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileSystem.html)
associated with this command.


### Data stream(s) sizing consideration

Some commands may send/receive large amounts of data over their STDIN/STDOUT/STDERR streams. Since (by default) the sending mechanism in SSHD is
**asynchronous** it may cause _Out of memory_ errors due to one side (client/server) generating `SSH_MSG_CHANNEL_DATA` or `SSH_MSG_CHANNEL_EXTENDED_DATA`
at a much higher rate than the other side can consume. This leads to a build-up of a packets backlog that eventually consumes all available memory
(as described in [SSHD-754](https://issues.apache.org/jira/browse/SSHD-754) and [SSHD-768](https://issues.apache.org/jira/browse/SSHD-768)). As of
version 1.7 one can register a `ChannelStreamPacketWriterResolver` at the client/server/session/channel level that can enable the user to replace
the raw channel with some throttling mechanism that will be used for stream packets. Such an (experimental) example is the `ThrottlingPacketWriter`
available in the `sshd-contrib` module. **Note:** if the `ChannelStreamPacketWriterResolver` returns a wrapper instance instead of a `Channel` then
it will be **closed** automatically when the stream using it is closed.