libssh  0.7.0
Chapter 1: A typical SSH session

A typical SSH session

A SSH session goes through the following steps:

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

#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);
}

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:

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;
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);
}

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;
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);
}

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:

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.

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)
{
int state, hlen;
unsigned char *hash = NULL;
char *hexa;
char buf[10];
state = ssh_is_server_known(session);
hlen = ssh_get_pubkey_hash(session, &hash);
if (hlen < 0)
return -1;
switch (state)
{
case SSH_SERVER_KNOWN_OK:
break; /* ok */
case SSH_SERVER_KNOWN_CHANGED:
fprintf(stderr, "Host key for server changed: it is now:\n");
ssh_print_hexa("Public key hash", hash, hlen);
fprintf(stderr, "For security reasons, connection will be stopped\n");
free(hash);
return -1;
case SSH_SERVER_FOUND_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");
free(hash);
return -1;
case SSH_SERVER_FILE_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");
/* fallback to SSH_SERVER_NOT_KNOWN behavior */
case SSH_SERVER_NOT_KNOWN:
hexa = ssh_get_hexa(hash, hlen);
fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
fprintf(stderr, "Public key hash: %s\n", hexa);
free(hexa);
if (fgets(buf, sizeof(buf), stdin) == NULL)
{
free(hash);
return -1;
}
if (strncasecmp(buf, "yes", 3) != 0)
{
free(hash);
return -1;
}
if (ssh_write_knownhost(session) < 0)
{
fprintf(stderr, "Error %s\n", strerror(errno));
free(hash);
return -1;
}
break;
case SSH_SERVER_ERROR:
fprintf(stderr, "Error %s", ssh_get_error(session));
free(hash);
return -1;
}
free(hash);
return 0;
}
See also
ssh_connect
ssh_disconnect
ssh_get_error
ssh_get_error_code
ssh_get_pubkey_hash
ssh_is_server_known
ssh_write_knownhost

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 ressources. In SSH, the two concepts are linked. After authentication, the server can grant the user access to several ressources such as port forwarding, shell, sftp subsystem, and so on.

libssh supports several methods of authentication:

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;
int rc;
char *password;
// 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_knowhost(), 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);
}
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;
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_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_free(channel);
return SSH_ERROR;
}
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
}
if (nbytes < 0)
{
ssh_channel_free(channel);
return SSH_ERROR;
}
ssh_channel_free(channel);
return SSH_OK;
}
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 (generaly 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.