Equinox Incubator - Console supportability design documentation
Overall design
The new console is based on Apache Felix Gogo shell, which is the reference implementation of RFC 147 - the Command Line Interface specification in OSGi. The purpose of the console is to extend Gogo functionality in relation to:- execution of Equinox-type commands
- ssh and telnet connectivity
- JAAS-based user authentication
- tab completion
- command line editing and command history
- Command adapter for Equinox-type commands
- Equinox built-in command provider, migrated to Gogo-type commands
- Tab completion
- Command line editing and history
- Telnet server
- Ssh server
- JAAS authentication
- Secure store for user information (usernames, passwords and roles)
Command Adapter for Equinox-type Commands
The Gogo shell implements the RFC 147 type commands. In this specification the commands are arbitrary classes, registered as services with two special properties:osgi.command.scope
- specifies the scope of the commandosgi.command.function
- specifies the commands provided by this service; this is a list of the names of public methods of the service class, which should be available for execution as commands
Therefore, the Gogo shell does not recognize the Equinox-type commands, provided by a class implementing the CommandProvider
interface. The purpose of the command adapter is to expose the Equinox-type
commands as Gogo commands for backwards compatibility. The new console bundle registers in its activator trackers for Equinox-type commands. When such a command is registered, the component creates a CommandProviderAdapter
object for
this command and registers it as a Gogo command. The CommandProviderAdapter
exposes all methods of the Equinox CommandProvider
as command methods (as a value of the property osgi.command.function
).
Then when one of these commands is called, the CommandProviderAdapter
receives the call and delegates it to the actual CommandProvider
object.
ConsoleSession
s to represent a single session to the console. Since the Gogo shell is ignorant of ConsoleSession
s, the new console provides support for these. In its activator, it
registers a tracker for ConsoleSession
s, and when one is registered, it opens a Gogo CommandSession
with the I/O streams of the registered ConsoleSession
.
Equinox Built-in Command Provider, Migrated to Gogo-type Commands
The new console is intended to replace the Equinox built-in console. Currently, in order to use the new console, one has to disable the built-in console by setting the configuration propertyosgi.console.enable.builtin=false
.
The CommandProvider
with the basic Equinox commands is registered in the built-in console, and when it is disabled, the provider does not get registered. That is why all of its commands (such as ss, bundles, bundle,...) are migrated
to Gogo-type commands and are registered by the new console. The commands have the same syntax and output as the original Equinox commands. The main difference between the implementations of the original and migrated commands is that the latter make
use of the Gogo features such as converters and formatters, to get their parameters in the right format, and to format their output, respectively. Also, the migrated commands do not have direct access to the internal Equinox classes and use services
such as PackageAdmin
and PlatformAdmin
to get some of the information they need.
Tab completion
Tab completion, as the name suggests, provides functionality to complete the current input with possible options. This functionality is provided with the following limitation - it works only for ssh and telnet connections. On a session which uses standard input, it does not work. The reason is that the OS buffers the standard input until it receives a new line, so the code cannot determine when the tab key is pressed. This problem does not occur with the socket input stream, with which work the telnet and ssh sessions.
The component provides completion for filenames, Gogo session variables' names and commands' names. An interface Completer
is defined, with a single method getCandidates()
. This method accepts as arguments
the command line and the current position in it, and returns a map, containing the possible candidates. The keys in the map are the completion candidates and the values are the positions in the command line, from which starts
the completion.
There are three implementors of the Completer
interface in the new console - for filenames, session variables' names and commands' names. Additionally, the console supports custom completers - a custom bundle may register
an implementor of the Completer
interface as a service, and it will also be called upon request for completion. This could be useful if the custom completer uses some knowledge about the semantics of a command
and provides more accurate completion than the default completers.
There is a completion handler, which aggregates all available completers. When a request for completion comes, the completion handler looks up any custom completers and calls the default completers and the custom completers. It then returns all completion candidates, returned by all available completers.
However, some heuristics is used when calling the completers. The custom completers, if any are available, are always called. From the default completers, some may not be called. For example, if the token for completion starts with the file protocol identifier, then only the filename completer is called. If the token starts with the identifier for a variable reference (the dollar sign), only the session variables' names completer is called.
The file name completer provides completion for both absolute and relative (to the current directory) filenames. If the token for completion ends with the file separator, then the possible completion candidates are all files in the folder before the separator.
If after the calls to all completers there is only one candidate, the current token on the command line is completed with it. If there are more than one completion candidates, they are printed in the console and the user can iterate through them by pressing the tab key multiple times. Each time the current completion candidate is placed in the command line and seen in the console.
When multiple completions are available, the implementation finds the longest common prefix of all completion candidates. If this prefix is longer than the token for completion, the token is completed with the remaining characters from the prefix before the user starts to iterate through the options.
Command Line Editing and History
The OS shell provides command line history and editing by default. So when running the console in the OS shell, on the standard input and output, these features come for granted. The situation when connecting through ssh and telnet is different. There the editing should be done in the code.
So the new console contains a component, which performs this function both for telnet and ssh. The component provides wrappers of the input and output socket steams. These wrappers take care of the editing and history. The input stream wrapper reads the socket input character by character and buffers it. Then editing operations (adding/deleting characters, moving with the arrows) are performed on the content in the buffer. The changed content is displayed in the output stream. The buffer content is passed to the actual input stream from which the shell reads, when new line comes from the socket.
The history is a buffer, which saves each command line when a new line character is read, just before passing it to the shell input stream. With the up and down arrow the user can navigate through the history.
Telnet server
The new console provides a telnet server. It can be started and stopped through the telnet
console command. Moreover, if a default value is provided for the telnet port through a configuration property, the server is started by default. The server listens
for connections on the telnet port. When a client connects to the socket, the server creates a TelnetConnection
object, which handles the socket input. Here again the input from the socket is wrapped in a wrapper, which handles the telnet
protocol specifics. The wrapper forwards the user input (which is not part of the telnet protocol) to the command line editing component, and also echoes it to the socket output stream.
First of all, before starting to handle user input, the wrapper performs terminal type negotiation with the telnet client. The terminal type determines the encoding of the control escape sequences. This enables the shell to handle correctly these escape sequences. The console recognizes the following control escape sequences:
- Up arrow
- Down arrow
- Right arrow
- Left arrow
- Home
- End
- Page up
- Page down
- Insert
BACKSPACE
and DEL
codes are also negotiated.
The console supports the following terminal types (the subsets of them, consisting of the above escape sequences):
- VT100
- VT220
- VT320
- SCO
- ANSI
- XTERM
From each terminal type the above described control escape sequences are supported.
If the client does not provide terminal type negotiation, or requests a different terminal type from the supported, the XTERM terminal type is used as default on UNIX/Linux systems and ANSI - on Windows systems. After the terminal type is negotiated,
the telnet component creates a Gogo CommandSession
and the console can proceed with processing the user input.
In addtion to the telnet port, the telnet server can be configured also with a host address, on which to listen for incoming connections. This allows to restrict the telnet server to listen only on a particular network adress on the local host, instead
of listening on all available addresses - this is the default behavior when opening a ServerSocket
if no host address is specified. The host and port may be specified through the configuration property osgi.console
. The format of the value of the property
is [<host>:]<port>
. If the host and/or port are not specified as a configuration property, they can be provided as arguments to the telnet
command, which starts the telnet server.
Ssh Server
The new console provides also a ssh server. It can be started and stopped through the ssh
console command. Moreover, if a default value is provided for the ssh port through a configuration property, the server is started by default. The server listens
for connections on the ssh port. For the internals of the ssh protocol the Apache SSHD library is used. After establishing the SSH connection, the terminal type is determined by an environment variable, provided by the SSHD server. Then a wrapper for the socket input
is created. It handles the echoing of the user input to the output of the socket and forwards the input to the command line editing component. Finally, a Gogo CommandSession
is created and the ssh components starts listening for user input.
The ssh supports the same terminal types as the telnet.
In addtion to the ssh port, the ssh server can be configured also with a host address, on which to listen for incoming connections. This is implemented for the same reasons as with the telnet server. The host and port may be specified through the configuration property
osgi.console.ssh
. The format of the value of the property is [<host>:]<port>
. If the host and/or port are not specified as a configuration property, they can be provided as arguments to the ssh
command, which starts the ssh server.
JAAS Authentication
The ssh server uses a simple JAAS login module for password-based user authentication. The login module uses the secure store to get the user's password and compares it with the password passed by the user. The module uses RolePrincipal
and
UserPrincipal
. The RolePrincipal
represents a single user role. The UserPrincipal
represents the user with username, password and a set of RolePrincipal
s. Currently the roles do not have a real function
in the console - authorization is not implemented yet. They are provided to facilitate the implementation of user authorization in the future.
Instead of the default JAAS login module, a custom login module can be specified.
The SSHD library, used by the ssh server, is configured to use JAAS authentication. In order for the login module to be found upon user authorization, a fragment bundle to the SSHD bundle is created. It does not provide any functionality and its sole purpose is to import the login module package (which is part of the console) to the SSHD bundle. This allows the SSHD bundle to load the login module class.
Secure Store
For the purpose of the password-based user authentication, the default ssh authentication mechanism uses a secure store to store users' passwords and roles. If the default authentication is to be used is specified with the property
osgi.console.ssh.useDefaultSecureStorage
. This property must be set to true in order to use the default authentication.
The default secure store stores the user information in a file on the filesystem. The location of the file must be specified with the property org.eclipse.equinox.console.jaas.file
.
The passwords are encrypted using one-way hashing.
The ssh server, when starting, checks if the default authentication and store should be used. If this is the case, it checks if there are users stored in the store. If there are no users, it creates a default user equinox
with password equinox
. The user is
required to create a new user upon first login and the default user is deleted. Also, the ssh server registers a command provider with commands for user management. The commands for the follwing operations are available:
- adding a user
- deleting a user
- setting/changing a password for a user
- resetting a password for a user
- adding roles for a user
- removing user's roles
- listing users
The user management commands use directly the API for managing the secure store. The API provides operations similar to the commands.
The commands for adding a user and setting a password have two versions - interactive and batch.
The interactive version does not have arguments and asks sequentially for username, password and roles, waiting for user input after each of these. It also requests confirmation of the password. The password is not echoed
to the socket output, so that it is not printed in the user's console. Also, the command history is turned off, so that the password is not saved there.
The batch version takes all arguments together.
The password must be at least 8 symbols long.
The username may contain alphanumerical characters, plus underscore and dot.
If upon confirmation the two passwords do not match, the user is asked two more times for confirmation.
The secure store API in each method reads the store file (which is a properties file) into a Properties
object, performs the operation upon them, and stores them back to the file.
Configuring SSH and Telnet Port and Host through Configuration Admin
The console supports configuring ssh and telnet port and host through Configuration Admin. When the SshCommand
and TelnetCommand
objects are created,
they check the value of the proprety osgi.console.useConfigAdmin
. If it is not set, or has value of false
, they use the configuration properties to get values for the host and/or port.
If the property is set to true
, they ignore the configuration properties and register a ManagedService
to receive configuration from the Configuration Admin.