mirror of
https://github.com/tmate-io/tmate.git
synced 2024-11-27 10:33:09 +01:00
456 lines
14 KiB
Plaintext
456 lines
14 KiB
Plaintext
/**
|
|
@page libssh_tutor_guided_tour Chapter 1: A typical SSH session
|
|
@section 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. DSA or RSA. 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 dsa 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".
|
|
|
|
|
|
@subsection setup 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.
|
|
@code
|
|
#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);
|
|
}
|
|
@endcode
|
|
|
|
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:
|
|
|
|
@code
|
|
#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);
|
|
}
|
|
@endcode
|
|
|
|
Please notice that all parameters are passed to ssh_options_set() as pointers,
|
|
even if you need to set an integer value.
|
|
|
|
@see ssh_new
|
|
@see ssh_free
|
|
@see ssh_options_set
|
|
@see ssh_options_parse_config
|
|
@see ssh_options_copy
|
|
@see ssh_options_getopt
|
|
|
|
|
|
@subsection connect 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:
|
|
|
|
@code
|
|
#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);
|
|
}
|
|
@endcode
|
|
|
|
|
|
@subsection serverauth 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_is_server_known()
|
|
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.
|
|
|
|
The following example is part of the examples suite available in the
|
|
examples/ directory:
|
|
|
|
@code
|
|
#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;
|
|
}
|
|
@endcode
|
|
|
|
@see ssh_connect
|
|
@see ssh_disconnect
|
|
@see ssh_get_error
|
|
@see ssh_get_error_code
|
|
@see ssh_get_pubkey_hash
|
|
@see ssh_is_server_known
|
|
@see ssh_write_knownhost
|
|
|
|
|
|
@subsection auth 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:
|
|
- "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:
|
|
|
|
@code
|
|
#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);
|
|
}
|
|
@endcode
|
|
|
|
@see @ref authentication_details
|
|
|
|
|
|
@subsection using_ssh 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:
|
|
|
|
@code
|
|
int show_remote_processes(ssh_session session)
|
|
{
|
|
ssh_channel channel;
|
|
int rc;
|
|
char buffer[256];
|
|
unsigned 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) != 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;
|
|
}
|
|
@endcode
|
|
|
|
@see @ref opening_shell
|
|
@see @ref remote_command
|
|
@see @ref sftp_subsystem
|
|
@see @ref scp_subsystem
|
|
|
|
|
|
@subsection errors 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.
|
|
|
|
ssh_get_error() and ssh_get_error_code() take a ssh_session as a parameter.
|
|
That's for thread safety, error messages that can be attached to a session
|
|
aren't static anymore. Any error that happens during ssh_options_xxx()
|
|
or ssh_connect() (i.e., outside of any session) can be retrieved by
|
|
giving NULL as argument.
|
|
|
|
The SFTP subsystem has its own error codes, in addition to libssh ones.
|
|
|
|
|
|
*/
|