libssh: Chapter 1: A typical SSH session

libssh 0.12.2 The SSH library

Loading...
Searching...
No Matches
Chapter 1: A typical SSH session

# A typical SSH session

A SSH session goes through the following steps:

- Before connecting to the server, you can set up if you wish one or other server public key authentication, i.e. RSA, ED25519 or ECDSA. You can choose cryptographic algorithms you trust and compression algorithms if any. You must of course set up the hostname.
- The connection is established. A secure handshake is made, and resulting from it, a public key from the server is gained. You MUST verify that the public key is legitimate, using for instance the MD5 fingerprint or the known hosts file.
- The client must authenticate: the classical ways are password, or public keys (from ecdsa, ed25519 and rsa key-pairs generated by openssh). If a SSH agent is running, it is possible to use it.
- Now that the user has been authenticated, you must open one or several channels. Channels are different subways for information into a single ssh connection. Each channel has a standard stream (stdout) and an error stream (stderr). You can theoretically open an infinity of channels.
- With the channel you opened, you can do several things:
  - Execute a single command.
  - Open a shell. You may want to request a pseudo-terminal before.
  - Invoke the sftp subsystem to transfer files.
  - Invoke the scp subsystem to transfer files.
  - Invoke your own subsystem. This is outside the scope of this document, but can be done.
- When everything is finished, just close the channels, and then the connection.

The sftp and scp subsystems use channels, but libssh hides them to the programmer. If you want to use those subsystems, instead of a channel, you'll usually open a "sftp session" or a "scp session".

## Creating the session and setting options

The most important object in a SSH connection is the SSH session. In order to allocate a new SSH session, you use ssh_new(). Don't forget to always verify that the allocation succeeded.

#include <libssh/libssh.h>
#include <stdlib.h>
int main()
{
ssh_session my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);
...
ssh_free(my_ssh_session);
}
ssh_new
LIBSSH_API ssh_session ssh_new(void)
Create a new ssh session.
Definition session.c:65
ssh_free
LIBSSH_API void ssh_free(ssh_session session)
Deallocate a SSH session handle.
Definition session.c:251

libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() does the allocation and ssh_free() does the contrary.

The ssh_options_set() function sets the options of the session. The most important options are:

- SSH_OPTIONS_HOST: the name of the host you want to connect to
- SSH_OPTIONS_PORT: the used port (default is port 22)
- SSH_OPTIONS_USER: the system user under which you want to connect
- SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed

The complete list of options can be found in the documentation of ssh_options_set(). The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, the local username of your account will be used.

Here is a small example of how to use it:

#include <libssh/libssh.h>
#include <stdlib.h>
int main()
{
ssh_session my_ssh_session = NULL;
int verbosity = SSH_LOG_PROTOCOL;
int port = 22;
my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port);
...
ssh_free(my_ssh_session);
}
SSH_LOG_PROTOCOL
@ SSH_LOG_PROTOCOL
Definition libssh.h:336
ssh_options_set
LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, const void *value)
This function can set all possible ssh options.
Definition options.c:834

Please notice that all parameters are passed to ssh_options_set() as pointers, even if you need to set an integer value.

See also
  ssh_new
  ssh_free
  ssh_options_set
  ssh_options_parse_config
  ssh_options_copy
  ssh_options_getopt

## Connecting to the server

Once all settings have been made, you can connect using ssh_connect(). That function will return SSH_OK if the connection worked, SSH_ERROR otherwise.

You can get the English error string with ssh_get_error() in order to show the user what went wrong. Then, use ssh_disconnect() when you want to stop the session.

Here's an example:

#include <libssh/libssh.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
ssh_session my_ssh_session = NULL;
int rc;
my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
rc = ssh_connect(my_ssh_session);
if (rc != SSH_OK)
{
fprintf(stderr, "Error connecting to localhost: %s\n",
ssh_get_error(my_ssh_session));
exit(-1);
}
...
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
}
ssh_get_error
LIBSSH_API const char * ssh_get_error(void *error)
Retrieve the error text message from the last error.
Definition error.c:128
ssh_connect
LIBSSH_API int ssh_connect(ssh_session session)
Connect to the ssh server.
Definition client.c:555
ssh_disconnect
LIBSSH_API void ssh_disconnect(ssh_session session)
Disconnect from a session (client or server).
Definition client.c:812

## Authenticating the server

Once you're connected, the following step is mandatory: you must check that the server you just connected to is known and safe to use (remember, SSH is about security and authentication).

There are two ways of doing this:

- The first way (recommended) is to use the ssh_session_is_known_server() function. This function will look into the known host file (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, and determine whether this host is present or not in the list.
- The second way is to use ssh_get_pubkey_hash() to get a binary version of the public key hash value. You can then use your own database to check if this public key is known and secure.

You can also use the ssh_get_pubkey_hash() to show the public key hash value to the user, in case he knows what the public key hash value is (some paranoid people write their public key hash values on paper before going abroad, just in case ...).

If the remote host is being used to for the first time, you can ask the user whether he/she trusts it. Once he/she concluded that the host is valid and worth being added in the known hosts file, you use ssh_write_knownhost() to register it in the known hosts file, or any other way if you use your own database.

Note: When GSSAPI key exchange is used, the server identity is already verified via mutual GSSAPI authentication initiated by the SSH client. In such cases, most SSH servers won't send their host keys at all. If it fits your use case (e.g., you don't expect the server to send a host key), you can use ssh_session_kex_is_gss() to check if GSSAPI key exchange indeed took place and skip the knownhosts check.

The following example is part of the examples suite available in the examples/ directory:

#include <errno.h>
#include <string.h>
int verify_knownhost(ssh_session session)
{
enum ssh_known_hosts_e state;
unsigned char *hash = NULL;
ssh_key srv_pubkey = NULL;
size_t hlen;
char buf[10];
char *p = NULL;
int cmp;
int rc;
/* If GSSAPI key exchange was used, the server identity was already
* verified via Kerberos mutual authentication (MIC). We might skip
* the host key verification, especially if we don't expect the server
* to send its key. Alternatively, we could proceed without this check
* and handle the scenario when the server does not provide its host key
* later. In that case, ssh_session_is_known_server will return
* SSH_KNOWN_HOSTS_UNKNOWN.
*/
if (ssh_session_kex_is_gss(session)) {
return 0;
}
rc = ssh_get_server_publickey(session, &srv_pubkey);
if (rc < 0) {
return -1;
}
rc = ssh_get_publickey_hash(srv_pubkey,
SSH_PUBLICKEY_HASH_SHA256,
&hash,
&hlen);
ssh_key_free(srv_pubkey);
if (rc < 0) {
return -1;
}
state = ssh_session_is_known_server(session);
switch (state) {
case SSH_KNOWN_HOSTS_OK:
/* OK */
break;
case SSH_KNOWN_HOSTS_CHANGED:
fprintf(stderr, "Host key for server changed: it is now:\n");
ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen);
fprintf(stderr, "For security reasons, connection will be stopped\n");
ssh_clean_pubkey_hash(&hash);
return -1;
case SSH_KNOWN_HOSTS_OTHER:
fprintf(stderr, "The host key for this server was not found but an other"
"type of key exists.\n");
fprintf(stderr, "An attacker might change the default server key to"
"confuse your client into thinking the key does not exist\n");
ssh_clean_pubkey_hash(&hash);
return -1;
case SSH_KNOWN_HOSTS_NOT_FOUND:
fprintf(stderr, "Could not find known host file.\n");
fprintf(stderr, "If you accept the host key here, the file will be"
"automatically created.\n");
/* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
case SSH_KNOWN_HOSTS_UNKNOWN:
fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
fprintf(stderr, "Public key hash: ");
ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen);
ssh_clean_pubkey_hash(&hash);
p = fgets(buf, sizeof(buf), stdin);
if (p == NULL) {
return -1;
}
cmp = strncasecmp(buf, "yes", 3);
if (cmp != 0) {
return -1;
}
rc = ssh_session_update_known_hosts(session);
if (rc < 0) {
fprintf(stderr, "Error %s\n", strerror(errno));
return -1;
}
break;
case SSH_KNOWN_HOSTS_ERROR:
fprintf(stderr, "Error %s", ssh_get_error(session));
ssh_clean_pubkey_hash(&hash);
return -1;
}
ssh_clean_pubkey_hash(&hash);
return 0;
}
ssh_key_free
LIBSSH_API void ssh_key_free(ssh_key key)
deallocate a SSH key
Definition pki.c:260
ssh_clean_pubkey_hash
LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash)
Deallocate the hash obtained by ssh_get_pubkey_hash.
Definition session.c:1265
ssh_get_server_publickey
LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key)
Get the server public key from a session.
Definition session.c:1282
ssh_get_publickey_hash
LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, enum ssh_publickey_hash_type type, unsigned char **hash, size_t *hlen)
Allocates a buffer with the hash of the public key.
Definition session.c:1337
ssh_print_hash
LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len)
Print a hash as a human-readable hex- or base64-string.
Definition dh.c:814
ssh_session_update_known_hosts
LIBSSH_API int ssh_session_update_known_hosts(ssh_session session)
Adds the currently connected server to the user known_hosts file.
Definition knownhosts.c:1022
ssh_session_kex_is_gss
LIBSSH_API bool ssh_session_kex_is_gss(ssh_session session)
Check if current session keys were exchanged using a GSSAPI key exchange method.
Definition session.c:538
ssh_session_is_known_server
LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session)
Check if the servers public key for the connected session is known.
Definition knownhosts.c:1334

See also
  ssh_connect
  ssh_disconnect
  ssh_get_error
  ssh_get_error_code
  ssh_get_server_publickey
  ssh_get_publickey_hash
  ssh_session_is_known_server
  ssh_session_update_known_hosts

## Authenticating the user

The authentication process is the way a service provider can identify a user and verify his/her identity. The authorization process is about enabling the authenticated user the access to resources. In SSH, the two concepts are linked. After authentication, the server can grant the user access to several resources such as port forwarding, shell, sftp subsystem, and so on.

libssh supports several methods of authentication:

- "none" method. This method allows to get the available authentications methods. It also gives the server a chance to authenticate the user with just his/her login. Some very old hardware uses this feature to fallback the user on a "telnet over SSH" style of login.
- password method. A password is sent to the server, which accepts it or not.
- keyboard-interactive method. The server sends several challenges to the user, who must answer correctly. This makes possible the authentication via a codebook for instance ("give code at 23:R on page 3").
- public key method. The host knows the public key of the user, and the user must prove he knows the associated private key. This can be done manually, or delegated to the SSH agent as we'll see later.

All these methods can be combined. You can for instance force the user to authenticate with at least two of the authentication methods. In that case, one speaks of "Partial authentication". A partial authentication is a response from authentication functions stating that your credential was accepted, but yet another one is required to get in.

The example below shows an authentication with password:

#include <libssh/libssh.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
ssh_session my_ssh_session = NULL;
int rc;
char *password = NULL;
// Open session and set options
my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
// Connect to server
rc = ssh_connect(my_ssh_session);
if (rc != SSH_OK)
{
fprintf(stderr, "Error connecting to localhost: %s\n",
ssh_get_error(my_ssh_session));
ssh_free(my_ssh_session);
exit(-1);
}
// Verify the server's identity
// For the source code of verify_knownhost(), check previous example
if (verify_knownhost(my_ssh_session) < 0)
{
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
exit(-1);
}
// Authenticate ourselves
password = getpass("Password: ");
rc = ssh_userauth_password(my_ssh_session, NULL, password);
if (rc != SSH_AUTH_SUCCESS)
{
fprintf(stderr, "Error authenticating with password: %s\n",
ssh_get_error(my_ssh_session));
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
exit(-1);
}
...
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
}
ssh_userauth_password
LIBSSH_API int ssh_userauth_password(ssh_session session, const char *username, const char *password)
Try to authenticate by password.
Definition auth.c:1741

See also
  A deeper insight on authentication

## Doing something

At this point, the authenticity of both server and client is established. Time has come to take advantage of the many possibilities offered by the SSH protocol: execute a remote command, open remote shells, transfer files, forward ports, etc.

The example below shows how to execute a remote command:

int show_remote_processes(ssh_session session)
{
ssh_channel channel = NULL;
int rc;
char buffer[256];
int nbytes;
channel = ssh_channel_new(session);
if (channel == NULL)
return SSH_ERROR;
rc = ssh_channel_open_session(channel);
if (rc != SSH_OK)
{
ssh_channel_free(channel);
return rc;
}
rc = ssh_channel_request_exec(channel, "ps aux");
if (rc != SSH_OK)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return rc;
}
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
while (nbytes > 0)
{
if (write(1, buffer, nbytes) != (unsigned int) nbytes)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
}
if (nbytes < 0)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_OK;
}
ssh_channel_send_eof
LIBSSH_API int ssh_channel_send_eof(ssh_channel channel)
Send an end of file on the channel.
Definition channels.c:1405
ssh_channel_close
LIBSSH_API int ssh_channel_close(ssh_channel channel)
Close a channel.
Definition channels.c:1466
ssh_channel_request_exec
LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd)
Run a shell command without an interactive shell.
Definition channels.c:2881
ssh_channel_read
LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr)
Reads data from a channel.
Definition channels.c:3143
ssh_channel_free
LIBSSH_API void ssh_channel_free(ssh_channel channel)
Close and free a channel.
Definition channels.c:1298
ssh_channel_new
LIBSSH_API ssh_channel ssh_channel_new(ssh_session session)
Allocate a new channel.
Definition channels.c:90
ssh_channel_open_session
LIBSSH_API int ssh_channel_open_session(ssh_channel channel)
Open a session channel (suited for a shell, not TCP forwarding).
Definition channels.c:1095

Each ssh_channel_request_exec() needs to be run on freshly created and connected (with ssh_channel_open_session()) channel.

See also
  Opening a remote shell
  Passing a remote command
  The SFTP subsystem
  The SCP subsystem

## Handling the errors

All the libssh functions which return an error value also set an English error message describing the problem.

Error values are typically SSH_ERROR for integer values, or NULL for pointers.

The function ssh_get_error() returns a pointer to the static error message.

ssh_error_code() returns the error code number : SSH_NO_ERROR, SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL, or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your request, but the situation is recoverable. The others mean something happened to the connection (some encryption problems, server problems, ...). SSH_INVALID_REQUEST means the library got some garbage from server, but might be recoverable. SSH_FATAL means the connection has an important problem and isn't probably recoverable.

Most of time, the error returned are SSH_FATAL, but some functions (generally the ssh_request_xxx ones) may fail because of server denying request. In these cases, SSH_REQUEST_DENIED is returned.

For thread safety, errors are bound to ssh_session objects. As long as your ssh_session object is not NULL, you can retrieve the last error message and error code from the ssh_session using ssh_get_error() and ssh_get_error_code() respectively.

The SFTP subsystem has its own error codes, in addition to libssh ones.