Skip to main content

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
The default authentication implemented for the ssh support is password-based. The console consists of the following main components:
  • 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 command
  • osgi.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.

The Equinox built-in console provides ConsoleSessions to represent a single session to the console. Since the Gogo shell is ignorant of ConsoleSessions, the new console provides support for these. In its activator, it registers a tracker for ConsoleSessions, 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 property osgi.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
In addition, the 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

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 RolePrincipals. 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.


Back to the top