aboutsummaryrefslogtreecommitdiff
path: root/doc/guided_tour.dox
blob: 5f7a1003b7db5da0d315767bb1b4ee5350e5d1ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/**
@page 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 remote commands, 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_commands
@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.


*/